<commit_before>//#pragma once
//#pragma execution_character_set("utf-8")
#include<iostream>
#include<string>
#include<fstream>
#include<algorithm>
#include<cstdlib>
#include <vector>
#include <utility>
#include <map>
#include "spikedetect.h"
#include "probe.h"
#include "Eigen/Dense"
using namespace std;
using namespace Eigen;

//ĬϹ캯
SpikeDetect::SpikeDetect(){
	for (int i = 0; i < samples; ++i)
		for (int j = 0; j < channels; ++j){
			traces[i][j] = 0.0;
			traces_t[i][j] = 0.0;
			crossing[i][j] = 0;
		}

	high_crossing = low_crossing = 0;
}

//ıļй
SpikeDetect::SpikeDetect(std::string  filename){
	std::ifstream fin(filename);
	float in[samples];
	for (int i = 0; i < samples; ++i)
		for (int j = 0; j < channels; ++j){
			fin >> in[j];
			traces[i][j] = in[j];
			crossing[i][j] = 0;
			traces_t[i][j] = in[j];
		}
	//һЩмĳʼ
	high_crossing = low_crossing = 0;
}

//
SpikeDetect :: ~SpikeDetect(){
}

//
void SpikeDetect :: output(){
	for (int i = 0; i < 10; i++){
		for (int j = 0; j < channels; j++)
			cout << traces[i][j]<<"  ";
		cout << endl;
	}
	cout << "========" << endl;
	for (int i = samples-10; i < samples; i++){
		for (int j = 0; j < channels; j++)
			cout << traces[i][j]<< "   ";
		cout << endl;
	}
	cout << high_crossing << " " << low_crossing << endl;
}

//˲
void SpikeDetect::filter(int rate, float high, float low, int order){
}

//ֵ
void SpikeDetect::threshold(){
	float median = 0.0;
	/*-------------------------λҪĽ-----------------*/
	int nums = samples * channels;
	float temp[samples * channels];
	int x = 0;
	for (int i = 0; i < samples; i++)
		for (int j = 0; j < channels; j++)
		{
			if (traces[i][j] < 0)
				temp[x++] = -traces[i][j];
			else
                temp[x ++]= traces[i][j];
		}
	std::stable_sort(temp,temp+nums);
	/*----------------------------------------------------------------*/
	if (nums % 2) median = temp[nums / 2];
	else median = (temp[nums / 2] + temp[nums / 2 + 1]) / 2.0;
	high_crossing = p.get_threshold_strong_std_factor() * median/0.6745;
	low_crossing = p.get_threshold_weak_std_factor() * median/0.6745;
}

//νзת
void SpikeDetect::transform(){
	if (p.get_detect_spikes_symbol() == -1){
		cout << "detect_spikes_symbol  ==  -1" << endl;
		for (int i = 0; i < samples; i++)
			for (int j = 0; j < channels; j++)
					traces_t[i][j] = -traces[i][j];
	}
	else if (p.get_detect_spikes_symbol() == 1){
		for (int i = 0; i < samples; i++)
			for (int j = 0; j < channels; j++)
				if (traces[i][j] < 0)
					traces_t[i][j] = -traces[i][j];
	}
}

//componments
void SpikeDetect::detect(map <int, vector < pair<int, int> >  > &comps){

	std::vector < std::pair<int, int> > mark;
	pair<int, int> temp;
	vector < pair<int, int> > comp;
	//map <int, vector < pair<int, int> >  > comps;

	int strong_nodes[samples][channels];
	int strong_label[samples * channels];
	int label[samples][channels];
	memset(label, 0, sizeof(label));
	memset(strong_nodes, 0, sizeof(strong_nodes));
	/****************************************/
	for (int i = 0; i < samples; i++)
		for (int j = 0; j < channels; j++)
		{
			if (traces_t[i][j] > high_crossing)
				crossing[i][j] = 2;
			else if (traces_t[i][j] > low_crossing)
				crossing[i][j] = 1;
		}
	/******************************************/
	for (int i = 0; i < samples; i++)
		for (int j = 0; j<channels; j++)
		{
			if (crossing[i][j]>0){
				temp = make_pair(i, j);
				mark.push_back(temp);
			}
			if (crossing[i][j] > 1)
				strong_nodes[i][j] = 1;
		}
	for (unsigned int i = 0; i < mark.size(); i++)
	{
		temp = mark[i];
		//printf("%d %d ", temp.first, temp.second);
	}
	/***********************************flood_fill algorithm*************************************************************/
	int c_label = 1;
	int join_size = p.get_connected_component_join_size();
	printf("------> weak crossing numbers: %d\n", mark.size());
	for (unsigned int i_s_ch = 0; i_s_ch < mark.size(); i_s_ch++){
		temp = mark[i_s_ch];
		int i_s = temp.first;
		int i_ch = temp.second;
		for (int j_s = max(i_s - join_size, 0); j_s <= i_s; j_s++){
			for (int j_ch = max(i_ch - 2, 0); j_ch <= min(i_ch + 2, 31); j_ch++){
				int adjlabel = label[j_s][j_ch];
				//printf(" j_s, j_ch, adjlabel---->:%d  %d  %d  \n", j_s, j_ch, adjlabel);
				if (adjlabel){
					int curlabel = label[i_s][i_ch];
					if (curlabel == 0){
						label[i_s][i_ch] = adjlabel;
						temp = make_pair(i_s, i_ch);
						comps[adjlabel].push_back(temp);
					}
					else if (curlabel != adjlabel){
						comp = comps[adjlabel];
						for (unsigned int k = 0; k < comp.size(); k++){
							temp = comp[k];
							comps[curlabel].push_back(temp);
							if (strong_label[adjlabel]){
								strong_label[adjlabel] = 0;
								strong_label[curlabel] = 1;
							}
						}
						comps.erase(adjlabel);
					}
					if (curlabel > 0 && crossing[i_s][i_ch]>1)
						strong_label[curlabel] = 1;
				}
			}
		}
		if (label[i_s][i_ch] == 0){
			label[i_s][i_ch] = c_label;
			temp = make_pair(i_s, i_ch);
			comps[c_label].push_back(temp);
			if (crossing[i_s][i_ch] == 2)
				strong_label[c_label] = 1;
			c_label += 1;
		}
	}
	/************************************************************************************************/
	printf("ssss\n");
	printf("%d\n", comps.size());
	vector < pair<int, int> > tmp;
	for (const auto &w : comps){
		int cc = w.first;
		printf("this is the %d spike's component", cc);
		tmp = w.second;
		for (unsigned int ii = 0; ii < tmp.size(); ii++)
			printf("[ %d %d ] ", tmp[ii].first, tmp[ii].second);
		printf("\n");
	}
	printf("ending...\n");
	getchar();
}

void SpikeDetect::flood_fill(int x, int y, int label)
{
	crossing[x][y] = label;
	if (x > 0 && crossing[x - 1][y] == 0)
		flood_fill(x - 1, y, label);
	if (y > 0 && crossing[x][y - 1] == 0)
		flood_fill(x, y - 1, label);
	if (y > 0 && crossing[x][y - 1] == 0)
		flood_fill(x, y - 1, label);
	if (x< samples && crossing[x + 1][y] == 0)
		flood_fill(x + 1, y, label);
	if (y< channels && crossing[x][y + 1] == 0)
		flood_fill(x, y + 1, label);
}

//void SpikeDetect::detect(map <int, vector < pair<int, int> >  > &comps,vector<int> ){
//	//strong_nodeиڸֵĵļ
//	vector <pair <int, int>> strong_node;
//	pair <int, int > temp;
//	//ŵ
//	//crossing
//	for (int i = 0; i < samples; i++)
//		for (int j = 0; j < channels; j++)
//		{
//			if (traces[i][j] > high_crossing)
//			{
//				crossing[i][j] = 2;
//				temp = make_pair(i, j);
//				strong_node.push_back(temp);
//			}
//			else if (traces[i][j] > low_crossing)
//				crossing[i][j] = 1;
//		}
//
//
//}

//ݸߵֵһźŵĵλֵ
float SpikeDetect::normalize(float x){
	float norm_x;
	norm_x = (x - low_crossing) / (high_crossing - low_crossing);
	if (norm_x > 1) norm_x = 1;
	if (norm_x < 0) norm_x = 0;
	return norm_x;
}

//componmenttraces_tȡwave,
void SpikeDetect::comp_wave(int s_min, int s_max, vector < pair<int, int> > tmp_comp, std::vector<std::vector<float> > &wave){
	for (unsigned int i = 0; i < tmp_comp.size(); i++){
		int x = tmp_comp[i].first;
		int y = tmp_comp[i].second;
		wave[x - s_min][y] = traces_t[x][y];
	}
}

//wavecomponmentmask
void SpikeDetect::mask(vector<float > &mask,vector<int > &mask_bin, std::vector<std::vector<float> > &wave){
	for (unsigned int i = 0; i < mask_bin.size(); i++){
		if (mask_bin[i]){
			float peak = wave[0][i];
			for (int j = 1; j < samples; j++)
				if (peak < wave[j][i])
					peak < wave[j][i];
			mask[i] = normalize(peak);
		}//if
	}//for
}

//ÿwaveʱ
void SpikeDetect::spike_sample_aligned(int s_min, int s_max,std::vector<std::vector<float> > &wave, float &s_aligned){
	for (int i = 0; i < s_max - s_min; i++)
	{
		float sum_t1 = 0.0, sum_t2 = 0.0;
		for (int j = 0; j < channels; j++)
		{
			float wave_n,wave_n_p;
			wave_n = normalize(wave[i][j]);
			wave_n_p = wave_n*wave_n;
			sum_t1 += (wave_n_p)*i;
			sum_t2 += wave_n_p;
		}
		s_aligned = sum_t1 / sum_t2 + s_min;
	}
}

//waveĶʱ䣬ȡwaveform(Ҫڴ˴ȥŵ)
void SpikeDetect::extract(float &s_aligned,std::vector <std::vector <float> > &waveform){
	int sa = (int)s_aligned;
	int s_start = (int)s_aligned - p.get_extract_s_before() - 1;
	int s_end = (int)s_aligned + p.get_extract_s_after() + 2;
	if (s_start < 0 && s_end >=samples){
		cout << "we think it's not  a wavrform!!!" << endl;
		return;
	}
	//tracesΪFilterĲ
	else if (s_start < 0 || s_end >= samples){
		for (int i = 0; i < s_end - s_start; i++)
			for (int j = 0; j < channels; j++){
				if (i + s_start < 0 || s_end >= samples) waveform[i][j] = 0.0;
				else waveform[i][j] = traces[i + s_start][j];
			}
	}
	else{
		for (int i = 0; i < s_end - s_start; i++)
			for (int j = 0; j < channels; j++)
				waveform[i][j] = traces[i + s_start][j];
	}
}

/*--------------------compute PCA--------------------------*/
void featurenormalize(MatrixXf &X)
{
	//ÿһάȾֵ
	MatrixXf meanval = X.colwise().mean();
	RowVectorXf meanvecRow = meanval;
	//ֵΪ0
	X.rowwise() -= meanvecRow;
}

void computeCov(MatrixXf &X, MatrixXf &C)
{
	//ЭC = XTX /( n-1);
	C = X.adjoint() * X;
	C = C.array() / (X.rows() - 1);
}

void computeEig(MatrixXf &C, MatrixXf &vec, MatrixXf &val){
	//ֵʹselfadjontն㷨ȥ㣬òvecval
	SelfAdjointEigenSolver<MatrixXf> eig(C);

	vec = eig.eigenvectors();
	val = eig.eigenvalues();
}

int computeDim(MatrixXf &val)
{
	int dim;
	double sum = 0;
	for (int i = val.rows() - 1; i >= 0; --i)
	{
		sum += val(i, 0);
		dim = i;

		if (sum / val.sum() >= 0.95)
			break;
	}
	return val.rows() - dim;
}
/*--------------------------------------------------------------*/
//waveformsɷ
void SpikeDetect::PCA(int n_spikes, std::vector< std::vector< std::vector <float> > >&waveforms, std::vector <std::vector <float> > &masks, std::vector< std::vector< std::vector <float> > >&out){
	int width_sample = p.get_extract_s_after() + p.get_extract_s_before();
	int n_pca = p.get_n_features_per_channel();
	std::vector< std::vector< std::vector <float> > >pcs(n_pca, std::vector<std::vector<float> >(width_sample, vector<float>(channels)));
	//δڱPCA֮pcsЩÿchannelһwidth_sample*n_pcapcs.shape= (n_pca , width_sample, n_channels)

	for (int i = 0; i < channels; i++)
	{
		int n_spike_ch = 0;
		for (int j = 0; j < n_spikes; j++)
			if (masks[j][i]> 0)
				n_spike_ch++;

		Eigen::MatrixXf cov_reg(width_sample, width_sample);
		Eigen::MatrixXf cov(width_sample, width_sample);
		for (int i = 0; i < width_sample; i++)
			cov_reg(i, i) = 1;

		if (n_spike_ch <= 1)
			cov = (1.0 / n_spike_ch) * cov_reg;
		else
		{
			Eigen::MatrixXf X(n_spike_ch, width_sample);
			for (int j = 0; j < n_spikes; j++)
				if (masks[j][i]> 0){
					for (int k = 0; k < width_sample; k++)
						X(j, k) = waveforms[j][k][i];
				}
			Eigen::MatrixXf cov_channel(width_sample, width_sample);
			featurenormalize(X);
			computeCov(X, cov_channel);
			cov = (1.0 / n_spike_ch) * cov_reg + cov_channel;
		}
		MatrixXf vec, val;
		computeEig(cov, vec, val);
		//ȡÿchannelϵǰn_pcaάpcs
		for (int np = 0; np < n_pca; np++)
			for (int ns = 0; ns < width_sample; ns++)
				pcs[np][ns][i] = vec(ns, width_sample - 1 - np);
	}
	//ͨpcsxxнά
	for (int nsp = 0; nsp < n_spikes; nsp++)
		for (int nf = 0; nf < n_pca; nf++)
		{
			float sum = 0.0;
			for (int ch = 0; ch < channels; ch++){
				for (int ns = 0; ns < width_sample; ns++)
					sum += pcs[nf][ch][ns] * waveforms[nsp][ch][ns];
				out[nsp][ch][nf] = sum;
			}
		}
}<commit_msg>Delete spikedetect.cpp<commit_after><|endoftext|>


<commit_before>#pragma once

#include "blackhole/attribute.hpp"
#include "blackhole/repository/factory/traits.hpp"
#include "blackhole/sink/files/backend.hpp"
#include "blackhole/sink/files/config.hpp"
#include "blackhole/sink/files/flusher.hpp"
#include "blackhole/sink/files/rotation.hpp"
#include "blackhole/sink/files/writer.hpp"
#include "blackhole/utils/unique.hpp"

namespace blackhole {

namespace sink {

template<class Backend, class Rotator, class = void>
class file_hander_t;

template<class Backend>
class file_hander_t<Backend, NoRotation, void> {
    Backend m_backend;
    files::writer_t<Backend> writer;
    files::flusher_t<Backend> flusher;
public:
    file_hander_t(const std::string& path, const files::config_t<NoRotation>& config) :
        m_backend(path),
        writer(m_backend),
        flusher(config.autoflush, m_backend)
    {}

    void handle(const std::string& message) {
        writer.write(message);
        flusher.flush();
    }

    const Backend& backend() {
        return m_backend;
    }
};

template<class Backend, class Rotator>
class file_hander_t<
    Backend,
    Rotator,
    typename std::enable_if<
        !std::is_same<Rotator, NoRotation>::value
    >::type>
{
    Backend m_backend;
    files::writer_t<Backend> writer;
    files::flusher_t<Backend> flusher;
    Rotator rotator;
public:
    file_hander_t(const std::string& path, const files::config_t<Rotator>& config) :
        m_backend(path),
        writer(m_backend),
        flusher(config.autoflush, m_backend),
        rotator(config.rotation, m_backend)
    {}

    void handle(const std::string& message) {
        writer.write(message);
        flusher.flush();
        if (rotator.necessary(message)) { //!@todo: Unlikely optimization.
            rotator.rotate();
        }
    }

    const Backend& backend() {
        return m_backend;
    }
};

template<class Backend = files::boost_backend_t, class Rotator = NoRotation>
class files_t {
    typedef file_hander_t<Backend, Rotator> file_handler_type;
    typedef std::map<std::string, std::shared_ptr<file_handler_type>> file_handlers_type;

    files::config_t<Rotator> config;
    file_handlers_type m_handlers;
public:
    typedef files::config_t<Rotator> config_type;

    static const char* name() {
        return "files";
    }

    files_t(const config_type& config) :
        config(config)
    {}

    //!@todo: second arg is temporary
    void consume(const std::string& message, const log::attributes_t& attributes = log::attributes_t()) {
        auto filename = make_filename(attributes);
        auto it = m_handlers.find(filename);
        if (it == m_handlers.end()) {
            it = m_handlers.insert(it, std::make_pair(filename, std::make_shared<file_handler_type>(filename, config)));
        }
        it->second->handle(message);
    }

    const file_handlers_type& handlers() {
        return m_handlers;
    }

    std::string make_filename(const log::attributes_t& attributes) const {
        auto it = config.path.begin();
        auto end = config.path.end();
        std::string filename;
        while (it != end) {
            if (*it == '%' && it + 1 != end && *(it + 1) == '(') {
                it += 2;

                std::string key;
                bool found = false;
                while (it != end) {
                    if (*it == ')' && it + 1 != end && *(it + 1) == 's') {
                        found = true;
                        it += 2;
                        break;
                    }
                    key.push_back(*it);
                    it++;
                }

                if (found) {
                    auto ait = attributes.find(key);
                    if (ait != attributes.end()) {
                        std::ostringstream stream;
                        stream << ait->second.value;
                        filename.append(stream.str());
                    } else {
                        filename.append(key);
                    }
                } else {
                    filename.append(key);
                }
            }

            if (it == end) {
                break;
            }
            filename.push_back(*it);
            it++;
        }
//        std::cout << "filename=" << filename << std::endl;
        return filename;
    }
};

} // namespace sink

template<class Backend, class Watcher>
struct unique_id_traits<sink::files_t<Backend, sink::rotator_t<Backend, Watcher>>> {
    typedef sink::rotator_t<Backend, Watcher> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;

    typedef std::map<std::string, boost::any> variant_map_t;

    static std::string generate(const boost::any& config) {
        const variant_map_t& cfg = boost::any_cast<variant_map_t>(config);

        auto rotation_it = cfg.find("rotation");
        if (rotation_it != cfg.end()) {
            const variant_map_t& rotation = boost::any_cast<variant_map_t>(rotation_it->second);

            const bool has_size_watcher = rotation.find("size") != rotation.end();
            const bool has_datetime_watcher = rotation.find("period") != rotation.end();

            if (has_size_watcher && has_datetime_watcher) {
                throw blackhole::error_t("set watcher is not implemented yet");
            } else if (has_size_watcher) {
                return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), "size");
            } else if (has_datetime_watcher) {
                return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), "datetime");
            }

            throw blackhole::error_t("rotation section not properly configures: no watcher settings found");
        }

        return sink_type::name();
    }
};

template<class Backend>
struct config_traits<sink::files_t<Backend, sink::NoRotation>> {
    static std::string name() {
        return sink::files_t<Backend, sink::NoRotation>::name();
    }
};

template<class Backend, class Watcher>
struct config_traits<sink::files_t<Backend, sink::rotator_t<Backend, Watcher>>> {
    typedef sink::rotator_t<Backend, Watcher> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;

    static std::string name() {
        return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), Watcher::name());
    }
};

namespace aux {

template<class Backend, class Rotator>
struct filler<sink::files_t<Backend, Rotator>> {
    template<class Extractor, class Config>
    static void extract_to(const Extractor& ex, Config& config) {
        ex["path"].to(config.path);
        ex["autoflush"].to(config.autoflush);
    }
};

template<class Backend, class Watcher>
struct filler<sink::rotator_t<Backend, Watcher>> {
    template<class Extractor, class Config>
    static void extract_to(const Extractor& ex, Config& config) {
        ex["rotation"]["pattern"].to(config.rotation.pattern);
        ex["rotation"]["backups"].to(config.rotation.backups);
    }
};

} // namespace aux

template<class Backend>
struct factory_traits<sink::files_t<Backend>> {
    typedef sink::files_t<Backend> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
    }
};

template<class Backend>
struct factory_traits<sink::files_t<Backend, sink::rotator_t<Backend, sink::rotation::watcher::size_t>>> {
    typedef sink::rotation::watcher::size_t watcher_type;
    typedef sink::rotator_t<Backend, watcher_type> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
        aux::filler<rotator_type>::extract_to(ex, config);
        ex["rotation"]["size"].to(config.rotation.watcher.size);
    }
};

template<class Backend>
struct factory_traits<sink::files_t<Backend, sink::rotator_t<Backend, sink::rotation::watcher::datetime_t<>>>> {
    typedef sink::rotation::watcher::datetime_t<> watcher_type;
    typedef sink::rotator_t<Backend, watcher_type> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
        aux::filler<rotator_type>::extract_to(ex, config);
        ex["rotation"]["period"].to(config.rotation.watcher.period);
    }
};

} // namespace blackhole
<commit_msg>[Aux] File rotation actually isn’t very often event.<commit_after>#pragma once

#include "blackhole/attribute.hpp"
#include "blackhole/repository/factory/traits.hpp"
#include "blackhole/sink/files/backend.hpp"
#include "blackhole/sink/files/config.hpp"
#include "blackhole/sink/files/flusher.hpp"
#include "blackhole/sink/files/rotation.hpp"
#include "blackhole/sink/files/writer.hpp"
#include "blackhole/utils/unique.hpp"

namespace blackhole {

namespace sink {

template<class Backend, class Rotator, class = void>
class file_hander_t;

template<class Backend>
class file_hander_t<Backend, NoRotation, void> {
    Backend m_backend;
    files::writer_t<Backend> writer;
    files::flusher_t<Backend> flusher;
public:
    file_hander_t(const std::string& path, const files::config_t<NoRotation>& config) :
        m_backend(path),
        writer(m_backend),
        flusher(config.autoflush, m_backend)
    {}

    void handle(const std::string& message) {
        writer.write(message);
        flusher.flush();
    }

    const Backend& backend() {
        return m_backend;
    }
};

template<class Backend, class Rotator>
class file_hander_t<
    Backend,
    Rotator,
    typename std::enable_if<
        !std::is_same<Rotator, NoRotation>::value
    >::type>
{
    Backend m_backend;
    files::writer_t<Backend> writer;
    files::flusher_t<Backend> flusher;
    Rotator rotator;
public:
    file_hander_t(const std::string& path, const files::config_t<Rotator>& config) :
        m_backend(path),
        writer(m_backend),
        flusher(config.autoflush, m_backend),
        rotator(config.rotation, m_backend)
    {}

    void handle(const std::string& message) {
        writer.write(message);
        flusher.flush();
        if (BOOST_UNLIKELY(rotator.necessary(message))) {
            rotator.rotate();
        }
    }

    const Backend& backend() {
        return m_backend;
    }
};

template<class Backend = files::boost_backend_t, class Rotator = NoRotation>
class files_t {
    typedef file_hander_t<Backend, Rotator> file_handler_type;
    typedef std::map<std::string, std::shared_ptr<file_handler_type>> file_handlers_type;

    files::config_t<Rotator> config;
    file_handlers_type m_handlers;
public:
    typedef files::config_t<Rotator> config_type;

    static const char* name() {
        return "files";
    }

    files_t(const config_type& config) :
        config(config)
    {}

    //!@todo: second arg is temporary
    void consume(const std::string& message, const log::attributes_t& attributes = log::attributes_t()) {
        auto filename = make_filename(attributes);
        auto it = m_handlers.find(filename);
        if (it == m_handlers.end()) {
            it = m_handlers.insert(it, std::make_pair(filename, std::make_shared<file_handler_type>(filename, config)));
        }
        it->second->handle(message);
    }

    const file_handlers_type& handlers() {
        return m_handlers;
    }

    std::string make_filename(const log::attributes_t& attributes) const {
        auto it = config.path.begin();
        auto end = config.path.end();
        std::string filename;
        while (it != end) {
            if (*it == '%' && it + 1 != end && *(it + 1) == '(') {
                it += 2;

                std::string key;
                bool found = false;
                while (it != end) {
                    if (*it == ')' && it + 1 != end && *(it + 1) == 's') {
                        found = true;
                        it += 2;
                        break;
                    }
                    key.push_back(*it);
                    it++;
                }

                if (found) {
                    auto ait = attributes.find(key);
                    if (ait != attributes.end()) {
                        std::ostringstream stream;
                        stream << ait->second.value;
                        filename.append(stream.str());
                    } else {
                        filename.append(key);
                    }
                } else {
                    filename.append(key);
                }
            }

            if (it == end) {
                break;
            }
            filename.push_back(*it);
            it++;
        }
//        std::cout << "filename=" << filename << std::endl;
        return filename;
    }
};

} // namespace sink

template<class Backend, class Watcher>
struct unique_id_traits<sink::files_t<Backend, sink::rotator_t<Backend, Watcher>>> {
    typedef sink::rotator_t<Backend, Watcher> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;

    typedef std::map<std::string, boost::any> variant_map_t;

    static std::string generate(const boost::any& config) {
        const variant_map_t& cfg = boost::any_cast<variant_map_t>(config);

        auto rotation_it = cfg.find("rotation");
        if (rotation_it != cfg.end()) {
            const variant_map_t& rotation = boost::any_cast<variant_map_t>(rotation_it->second);

            const bool has_size_watcher = rotation.find("size") != rotation.end();
            const bool has_datetime_watcher = rotation.find("period") != rotation.end();

            if (has_size_watcher && has_datetime_watcher) {
                throw blackhole::error_t("set watcher is not implemented yet");
            } else if (has_size_watcher) {
                return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), "size");
            } else if (has_datetime_watcher) {
                return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), "datetime");
            }

            throw blackhole::error_t("rotation section not properly configures: no watcher settings found");
        }

        return sink_type::name();
    }
};

template<class Backend>
struct config_traits<sink::files_t<Backend, sink::NoRotation>> {
    static std::string name() {
        return sink::files_t<Backend, sink::NoRotation>::name();
    }
};

template<class Backend, class Watcher>
struct config_traits<sink::files_t<Backend, sink::rotator_t<Backend, Watcher>>> {
    typedef sink::rotator_t<Backend, Watcher> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;

    static std::string name() {
        return utils::format("%s/%s/%s", sink_type::name(), rotator_type::name(), Watcher::name());
    }
};

namespace aux {

template<class Backend, class Rotator>
struct filler<sink::files_t<Backend, Rotator>> {
    template<class Extractor, class Config>
    static void extract_to(const Extractor& ex, Config& config) {
        ex["path"].to(config.path);
        ex["autoflush"].to(config.autoflush);
    }
};

template<class Backend, class Watcher>
struct filler<sink::rotator_t<Backend, Watcher>> {
    template<class Extractor, class Config>
    static void extract_to(const Extractor& ex, Config& config) {
        ex["rotation"]["pattern"].to(config.rotation.pattern);
        ex["rotation"]["backups"].to(config.rotation.backups);
    }
};

} // namespace aux

template<class Backend>
struct factory_traits<sink::files_t<Backend>> {
    typedef sink::files_t<Backend> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
    }
};

template<class Backend>
struct factory_traits<sink::files_t<Backend, sink::rotator_t<Backend, sink::rotation::watcher::size_t>>> {
    typedef sink::rotation::watcher::size_t watcher_type;
    typedef sink::rotator_t<Backend, watcher_type> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
        aux::filler<rotator_type>::extract_to(ex, config);
        ex["rotation"]["size"].to(config.rotation.watcher.size);
    }
};

template<class Backend>
struct factory_traits<sink::files_t<Backend, sink::rotator_t<Backend, sink::rotation::watcher::datetime_t<>>>> {
    typedef sink::rotation::watcher::datetime_t<> watcher_type;
    typedef sink::rotator_t<Backend, watcher_type> rotator_type;
    typedef sink::files_t<Backend, rotator_type> sink_type;
    typedef typename sink_type::config_type config_type;

    static void map_config(const aux::extractor<sink_type>& ex, config_type& config) {
        aux::filler<sink_type>::extract_to(ex, config);
        aux::filler<rotator_type>::extract_to(ex, config);
        ex["rotation"]["period"].to(config.rotation.watcher.period);
    }
};

} // namespace blackhole
<|endoftext|>


<commit_before>/*
 * This file is part of TelepathyQt4Logger
 *
 * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <QDebug>
#include <TelepathyQt4/Account>
#include <TelepathyQt4Logger/Constants>
#include <TelepathyQt4Logger/Entity>
#include <TelepathyQt4Logger/PendingEvents>
#include <TelepathyQt4Logger/TextEvent>
#include <TelepathyQt4Logger/utils.h>
#include <TelepathyQt4Logger/_gen/pending-events.moc.hpp>
#include <glib/gerror.h>
#include <glib/gdate.h>
#include <telepathy-logger/log-manager.h>
#include <telepathy-logger/event.h>
#include <telepathy-logger/text-event.h>

using namespace Tpl;

struct TELEPATHY_QT4_LOGGER_NO_EXPORT PendingEvents::Private
{
    LogManagerPtr manager;
    Tp::AccountPtr account;
    EntityPtr entity;
    EventTypeMask typeMask;
    QDate date;

    bool filtered;
    uint numEvents;
    LogEventFilter filterFunction;
    void *filterFunctionUserData;

    EventPtrList events;

    static void callback(void *logManager, void *result, PendingEvents *self);
    static gboolean eventFilterMethod(TplEvent *event, gpointer *user_data);
};

PendingEvents::PendingEvents(LogManagerPtr manager, Tp::AccountPtr account, EntityPtr entity,
    EventTypeMask typeMask, const QDate &date)
    : PendingOperation(),
      mPriv(new Private())
{
    mPriv->manager = manager;
    mPriv->account = account;
    mPriv->entity = entity;
    mPriv->typeMask = typeMask;
    mPriv->date = date;
    mPriv->filtered = false;
    mPriv->filterFunction = 0;
    mPriv->filterFunctionUserData = 0;
}

PendingEvents::PendingEvents(LogManagerPtr manager, Tp::AccountPtr account, EntityPtr entity,
    EventTypeMask typeMask, uint numEvents, LogEventFilter filterFunction, void *filterFunctionUserData)
    : PendingOperation(),
      mPriv(new Private())
{
    mPriv->manager = manager;
    mPriv->account = account;
    mPriv->entity = entity;
    mPriv->typeMask = typeMask;
    mPriv->filtered = true;
    mPriv->numEvents = numEvents;
    mPriv->filterFunction = filterFunction;
    mPriv->filterFunctionUserData = filterFunctionUserData;
}

PendingEvents::~PendingEvents()
{
    delete mPriv;
}

void PendingEvents::start()
{
    if (mPriv->filtered) {
        tpl_log_manager_get_filtered_events_async(mPriv->manager,
            Utils::instance()->tpAccount(mPriv->account),
            mPriv->entity,
            mPriv->typeMask,
            mPriv->numEvents,
            (TplLogEventFilter) Private::eventFilterMethod,
            this,
            (GAsyncReadyCallback) Private::callback,
            this);
    } else {
        GDate *gdate = g_date_new_dmy(
            mPriv->date.day(),
            (GDateMonth) mPriv->date.month(),
            mPriv->date.year());
        tpl_log_manager_get_events_for_date_async(mPriv->manager,
            Utils::instance()->tpAccount(mPriv->account),
            mPriv->entity,
            mPriv->typeMask,
            gdate,
            (GAsyncReadyCallback) Private::callback,
            this);
        g_date_free(gdate);
    }
}

EventPtrList PendingEvents::events() const
{
    if (!isFinished()) {
        qWarning() << "PendingEvents::events called before finished, returning empty";
        return EventPtrList();
    } else if (!isValid()) {
        qWarning() << "PendingEvents::events called when not valid, returning empty";
        return EventPtrList();
    }

    return mPriv->events;
}

void PendingEvents::Private::callback(void *logManager, void *result, PendingEvents *self)
{
    if (!TPL_IS_LOG_MANAGER(logManager)) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Invalid log manager in callback");
        return;
    }

    if (!G_IS_ASYNC_RESULT(result)) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Invalid async result in callback");
        return;
    }

    GList *events = NULL;
    GError *error = NULL;
    gboolean success = FALSE;

    if (self->mPriv->filtered) {
        success = tpl_log_manager_get_filtered_events_finish(TPL_LOG_MANAGER(logManager), G_ASYNC_RESULT(result), &events, &error);
    } else {
        success = tpl_log_manager_get_events_for_date_finish(TPL_LOG_MANAGER(logManager), G_ASYNC_RESULT(result), &events, &error);
    }

    if (error) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, error->message);
        g_error_free(error);
        return;
    }

    if (!success) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Query failed without specific error");
        return;
    }

    GList *i;
    for (i = events; i; i = i->next) {
        TplEvent * item = (TplEvent *) i->data;
        if (TPL_IS_TEXT_EVENT(item)) {
            TextEventPtr eventPtr = TextEventPtr::wrap(TPL_TEXT_EVENT(item), false);
            self->mPriv->events << eventPtr;
        } else if (TPL_IS_EVENT(item)) {
            EventPtr eventPtr = EventPtr::wrap(TPL_EVENT(item), false);
            self->mPriv->events << eventPtr;
        }
    }

    //g_list_foreach(dates, (GFunc) , NULL);
    g_list_free(events);

    self->setFinished();
}

gboolean PendingEvents::Private::eventFilterMethod(TplEvent *event, gpointer *user_data)
{
    PendingEvents *self = (PendingEvents *) user_data;
    if (!self) {
        return FALSE;
    }

    return self->mPriv->filterFunction(EventPtr::wrap(event, false), self->mPriv->filterFunctionUserData);
}
<commit_msg>Fix bug in EventPtr and TextEventPtr reference counting<commit_after>/*
 * This file is part of TelepathyQt4Logger
 *
 * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <QDebug>
#include <TelepathyQt4/Account>
#include <TelepathyQt4Logger/Constants>
#include <TelepathyQt4Logger/Entity>
#include <TelepathyQt4Logger/PendingEvents>
#include <TelepathyQt4Logger/TextEvent>
#include <TelepathyQt4Logger/utils.h>
#include <TelepathyQt4Logger/_gen/pending-events.moc.hpp>
#include <glib/gerror.h>
#include <glib/gdate.h>
#include <telepathy-logger/log-manager.h>
#include <telepathy-logger/event.h>
#include <telepathy-logger/text-event.h>

using namespace Tpl;

struct TELEPATHY_QT4_LOGGER_NO_EXPORT PendingEvents::Private
{
    LogManagerPtr manager;
    Tp::AccountPtr account;
    EntityPtr entity;
    EventTypeMask typeMask;
    QDate date;

    bool filtered;
    uint numEvents;
    LogEventFilter filterFunction;
    void *filterFunctionUserData;

    EventPtrList events;

    static void callback(void *logManager, void *result, PendingEvents *self);
    static gboolean eventFilterMethod(TplEvent *event, gpointer *user_data);
};

PendingEvents::PendingEvents(LogManagerPtr manager, Tp::AccountPtr account, EntityPtr entity,
    EventTypeMask typeMask, const QDate &date)
    : PendingOperation(),
      mPriv(new Private())
{
    mPriv->manager = manager;
    mPriv->account = account;
    mPriv->entity = entity;
    mPriv->typeMask = typeMask;
    mPriv->date = date;
    mPriv->filtered = false;
    mPriv->filterFunction = 0;
    mPriv->filterFunctionUserData = 0;
}

PendingEvents::PendingEvents(LogManagerPtr manager, Tp::AccountPtr account, EntityPtr entity,
    EventTypeMask typeMask, uint numEvents, LogEventFilter filterFunction, void *filterFunctionUserData)
    : PendingOperation(),
      mPriv(new Private())
{
    mPriv->manager = manager;
    mPriv->account = account;
    mPriv->entity = entity;
    mPriv->typeMask = typeMask;
    mPriv->filtered = true;
    mPriv->numEvents = numEvents;
    mPriv->filterFunction = filterFunction;
    mPriv->filterFunctionUserData = filterFunctionUserData;
}

PendingEvents::~PendingEvents()
{
    delete mPriv;
}

void PendingEvents::start()
{
    if (mPriv->filtered) {
        tpl_log_manager_get_filtered_events_async(mPriv->manager,
            Utils::instance()->tpAccount(mPriv->account),
            mPriv->entity,
            mPriv->typeMask,
            mPriv->numEvents,
            (TplLogEventFilter) Private::eventFilterMethod,
            this,
            (GAsyncReadyCallback) Private::callback,
            this);
    } else {
        GDate *gdate = g_date_new_dmy(
            mPriv->date.day(),
            (GDateMonth) mPriv->date.month(),
            mPriv->date.year());
        tpl_log_manager_get_events_for_date_async(mPriv->manager,
            Utils::instance()->tpAccount(mPriv->account),
            mPriv->entity,
            mPriv->typeMask,
            gdate,
            (GAsyncReadyCallback) Private::callback,
            this);
        g_date_free(gdate);
    }
}

EventPtrList PendingEvents::events() const
{
    if (!isFinished()) {
        qWarning() << "PendingEvents::events called before finished, returning empty";
        return EventPtrList();
    } else if (!isValid()) {
        qWarning() << "PendingEvents::events called when not valid, returning empty";
        return EventPtrList();
    }

    return mPriv->events;
}

void PendingEvents::Private::callback(void *logManager, void *result, PendingEvents *self)
{
    if (!TPL_IS_LOG_MANAGER(logManager)) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Invalid log manager in callback");
        return;
    }

    if (!G_IS_ASYNC_RESULT(result)) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Invalid async result in callback");
        return;
    }

    GList *events = NULL;
    GError *error = NULL;
    gboolean success = FALSE;

    if (self->mPriv->filtered) {
        success = tpl_log_manager_get_filtered_events_finish(TPL_LOG_MANAGER(logManager), G_ASYNC_RESULT(result), &events, &error);
    } else {
        success = tpl_log_manager_get_events_for_date_finish(TPL_LOG_MANAGER(logManager), G_ASYNC_RESULT(result), &events, &error);
    }

    if (error) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, error->message);
        g_error_free(error);
        return;
    }

    if (!success) {
        self->setFinishedWithError(TELEPATHY_QT4_LOGGER_ERROR_INVALID_ARGUMENT, "Query failed without specific error");
        return;
    }

    GList *i;
    for (i = events; i; i = i->next) {
        TplEvent * item = (TplEvent *) i->data;
        if (TPL_IS_TEXT_EVENT(item)) {
            TextEventPtr eventPtr = TextEventPtr::wrap(TPL_TEXT_EVENT(item), true);
            self->mPriv->events << eventPtr;
        } else if (TPL_IS_EVENT(item)) {
            EventPtr eventPtr = EventPtr::wrap(TPL_EVENT(item), true);
            self->mPriv->events << eventPtr;
        }
    }

    //g_list_foreach(dates, (GFunc) , NULL);
    g_list_free(events);

    self->setFinished();
}

gboolean PendingEvents::Private::eventFilterMethod(TplEvent *event, gpointer *user_data)
{
    PendingEvents *self = (PendingEvents *) user_data;
    if (!self) {
        return FALSE;
    }

    return self->mPriv->filterFunction(EventPtr::wrap(event, false), self->mPriv->filterFunctionUserData);
}
<|endoftext|>


<commit_before>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifdef _MSC_VER
#pragma warning( 4: 4786)
#endif

#include "OptionParser.h"

OptionParser::OptionParser(const std::string& helpPrefix,
			   const std::string& usageSuffix)
  : helpPre(helpPrefix), usageSuf(usageSuffix) {

}

OptionParser::~OptionParser() {
  ParserMap::iterator iter;
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    delete iter->second;
}

const std::string& OptionParser::getError() const {
  return error;
}

const std::vector<std::string>& OptionParser::getParameters() const {
  return parameters;
}

bool OptionParser::parse(int argc, char** argv) {
  parameters.clear();
  error = "";
  ParserMap::iterator iter;
  for (int i = 1; i < argc; ++i) {
    if (!strcmp(argv[i], "-help")) {
      printHelp(std::cout, argv[0]);
      return false;
    }
    if (argv[i][0] != '-')
      parameters.push_back(argv[i]);
    else {
      iter = parsers.find(&argv[i][1]);
      if (iter == parsers.end()) {
	error = error + "Unknown option \"" + argv[i] + "\"";
	break;
      }
      i += iter->second->parse(&argv[i+1]);
    }
  }
  if (error.size() > 0) {
    std::cerr<<error<<std::endl;
    printUsage(std::cerr, argv[0]);
    std::cerr<<std::endl;
    return false;
  }
  return true;
}

void OptionParser::printHelp(std::ostream& os, const std::string& progName) const {
  os<<helpPre<<std::endl<<std::endl;
  printUsage(os, progName);
  os<<std::endl<<std::endl;
  ParserMap::const_iterator iter;
  os<<"   -help: print this help message"<<std::endl;
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    os<<"   -"<<iter->first<<": "<<iter->second->help<<std::endl;
}

void OptionParser::printUsage(std::ostream& os, const std::string& progName) const {
  ParserMap::const_iterator iter;
  os<<"Usage: "<<progName<<" [-help] ";
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    os<<iter->second->usage<<" ";
  os<<usageSuf;
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>bzadmin password hiding<commit_after>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifdef _MSC_VER
#pragma warning( 4: 4786)
#endif

#include "OptionParser.h"

OptionParser::OptionParser(const std::string& helpPrefix,
			   const std::string& usageSuffix)
  : helpPre(helpPrefix), usageSuf(usageSuffix) {

}

OptionParser::~OptionParser() {
  ParserMap::iterator iter;
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    delete iter->second;
}

const std::string& OptionParser::getError() const {
  return error;
}

const std::vector<std::string>& OptionParser::getParameters() const {
  return parameters;
}

bool OptionParser::parse(int argc, char** argv) {
  parameters.clear();
  error = "";
  ParserMap::iterator iter;
  for (int i = 1; i < argc; ++i) {
    if (!strcmp(argv[i], "-help")) {
      printHelp(std::cout, argv[0]);
      return false;
    }
    if (argv[i][0] != '-') {
      parameters.push_back(argv[i]);
      // should just blank out the password, but we don't really parse it here
      memset(argv[i], ' ', strlen(argv[i]));
    } else {
      iter = parsers.find(&argv[i][1]);
      if (iter == parsers.end()) {
	error = error + "Unknown option \"" + argv[i] + "\"";
	break;
      }
      i += iter->second->parse(&argv[i+1]);
    }
  }
  if (error.size() > 0) {
    std::cerr<<error<<std::endl;
    printUsage(std::cerr, argv[0]);
    std::cerr<<std::endl;
    return false;
  }
  return true;
}

void OptionParser::printHelp(std::ostream& os, const std::string& progName) const {
  os<<helpPre<<std::endl<<std::endl;
  printUsage(os, progName);
  os<<std::endl<<std::endl;
  ParserMap::const_iterator iter;
  os<<"   -help: print this help message"<<std::endl;
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    os<<"   -"<<iter->first<<": "<<iter->second->help<<std::endl;
}

void OptionParser::printUsage(std::ostream& os, const std::string& progName) const {
  ParserMap::const_iterator iter;
  os<<"Usage: "<<progName<<" [-help] ";
  for (iter = parsers.begin(); iter != parsers.end(); ++iter)
    os<<iter->second->usage<<" ";
  os<<usageSuf;
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 David Wicks, sansumbrella.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Timeline.h"

using namespace choreograph;

void Timeline::step( float dt )
{
  // Remove any motions that have stale pointers.
  // It would be cool (and 10-15% faster) if isValid() were not a virtual method.
  erase_if( &_motions, [] ( const MotionBaseRef &motion ) { return ! motion->isValid(); } );

  // Update all animation outputs.
  for( auto &c : _motions ) {
    c->step( dt );
  }

  // Remove any completed motions
  if( _auto_clear ) {
    erase_if( &_motions, [] ( const MotionBaseRef &motion ) { return motion->isFinished(); } );
  }
}

void Timeline::remove( const MotionBaseRef &motion )
{
  vector_remove( &_motions, motion );
}

void Timeline::cue( const std::function<void ()> &fn, float delay )
{
  _motions.push_back( std::make_shared<Cue>( fn, delay ) );
}
<commit_msg>Single pass for erasure.<commit_after>/*
 * Copyright (c) 2014 David Wicks, sansumbrella.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Timeline.h"

using namespace choreograph;

void Timeline::step( float dt )
{
  // Remove any motions that have stale pointers or that have completed playing.
  bool do_clear = _auto_clear;
  erase_if( &_motions, [do_clear] ( const MotionBaseRef &motion ) { return (do_clear && motion->isFinished()) || (! motion->isValid()); } );

  // Update all animation outputs.
  for( auto &c : _motions ) {
    c->step( dt );
  }
}

void Timeline::remove( const MotionBaseRef &motion )
{
  vector_remove( &_motions, motion );
}

void Timeline::cue( const std::function<void ()> &fn, float delay )
{
  _motions.push_back( std::make_shared<Cue>( fn, delay ) );
}
<|endoftext|>


<commit_before>/*
	This file implements the classes defined in Match.h:

	Match, Capability, and Client

	A Match object contains all of the information a startd needs
    about a given match, such as the capability, the client of this
    match, etc.  The capability in the match is just a pointer to a
    Capability object.  The client is also just a pointer to a Client
    object.  The startd maintains two Match objects in the "rip", the
    per-resource information structure.  One for the current match,
    and one for the possibly preempting match that is pending.

	A Capability object contains the capability string, and some
	functions to manipulate and compare against this string.  The
	constructor generates a new capability with the following form:
	<ip:port>#random_integer

	A Client object contains all the info about a given client of a
	startd.  In particular, the client name (a.k.a. "user"), the
	address ("<www.xxx.yyy.zzz:pppp>" formed ip/port), and the
	hostname.

	Written 9/29/97 by Derek Wright <wright@cs.wisc.edu>
*/

#include "startd.h"
static char *_FileName_ = __FILE__;

///////////////////////////////////////////////////////////////////////////
// Match
///////////////////////////////////////////////////////////////////////////

Match::Match()
{
	m_client = new Client;
	m_cap = new Capability;
	m_ad = NULL;
	m_rank = -1;
	m_oldrank = -1;
	m_universe = -1;
	m_agentstream = NULL;
	m_match_tid = -1;
	m_claim_tid = -1;
	m_aliveint = -1;
}

Match::~Match()
{
		// Cancel any timers associated with this match
	this->cancel_match_timer();
	this->cancel_claim_timer();

		// Free up memory that's been allocated
	if( m_ad ) {
		delete( m_ad );
	}
	delete( m_cap );
	if( m_client ) {
		delete( m_client );
	}
		// We don't want to delete m_agentstream, since DaemonCore
		// will delete it for us.
//	delete( m_agentstream );
}

void
Match::vacate()
{
	assert( m_cap );
		// warn the client of this match that it's being vacated
	if( m_client && m_client->addr() ) {
		m_client->vacate( m_cap->capab() );
	}
}

int
Match::send_accountant( int cmd )
{
	if( !accountant_host ) {
		return FALSE;
	}
	ReliSock sock;
	sock.timeout( 30 );
	if( ! sock.connect( accountant_host, ACCOUNTANT_PORT ) ) {
		dprintf(D_ALWAYS, "Couldn't connect to accountant\n");
		return FALSE;
	}
	if( !sock.put( cmd ) ) {
		dprintf(D_ALWAYS, "Can't send command (%d) to accountant\n", cmd );
		return FALSE;
	}
	if( !sock.put( m_cap->capab() ) ) {
		dprintf(D_ALWAYS, "Can't send capability to accountant\n");
		return FALSE;
	}
	if( !sock.eom() ) {
		dprintf(D_ALWAYS, "Can't send EOM to accountant\n");
		return FALSE;
	}
	sock.close();
	return TRUE;
}

void
Match::update( ClassAd* ad )
{
	char line[1024];
	char* tmp;

			// Update the current rank value
	sprintf( line, "%s = %f", ATTR_CURRENT_RANK, m_rank );
	ad->Insert( line );

	if( m_client ) {
		tmp = m_client->name();
		if( tmp ) {
			sprintf( line, "%s=\"%s\"", ATTR_REMOTE_USER, tmp );
			ad->Insert( line );
		}
		tmp = m_client->host();
		if( tmp ) {
			sprintf( line, "%s=\"%s\"", ATTR_CLIENT_MACHINE, tmp );
			ad->Insert( line );
		}
	}
}

void
Match::start_match_timer()
{
	m_match_tid =
		daemonCore->Register_Timer( match_timeout, 0,
								   (TimerHandlercpp)match_timed_out,
								   "match_timed_out", this );
	if( m_match_tid == -1 ) {
		EXCEPT( "Couldn't register timer (out of memory)." );
	}
	dprintf( D_FULLDEBUG, "Started match timer for %d seconds.\n",
			 match_timeout );
}

void
Match::cancel_match_timer()
{
	if( m_match_tid != -1 ) {
		daemonCore->Cancel_Timer( m_match_tid );
		m_match_tid = -1;
		dprintf( D_FULLDEBUG, "Canceled match timer.\n" );
	}
}

int
Match::match_timed_out()
{
	Resource* rip = resmgr->get_by_any_cap( capab() );
	if( !rip ) {
		EXCEPT( "Can't find resource of expired match." );
	}

	if( rip->r_cur->cap()->matches( capab() ) ) {
		if( rip->state() != matched_state ) {
			EXCEPT( "Match timed out but not in matched state." );
		}
		delete rip->r_cur;
		rip->r_cur = new Match;
		rip->change_state( owner_state );
	} else {
			// The match that timed out was the preempting match.
		assert( rip->r_pre->cap()->matches( capab() ) );
			// We need to generate a new preempting match object,
			// restore our reqexp, and update the CM.
		delete rip->r_pre;
		rip->r_pre = new Match;
		rip->r_reqexp->pub();
		rip->update();
	}
	return TRUE;
}

void
Match::start_claim_timer()
{
	if( m_aliveint < 0 ) {
		dprintf( D_ALWAYS,
				 "Warning: starting claim timer before alive interval set.\n" );
		m_aliveint = 300;
	}
	m_claim_tid =
		daemonCore->Register_Timer( (3 * m_aliveint), 0,
									(TimerHandlercpp)claim_timed_out,
									"claim_timed_out", this );
	if( m_claim_tid == -1 ) {
		EXCEPT( "Couldn't register timer (out of memory)." );
	}
	dprintf( D_FULLDEBUG, "Started claim timer w/ %d second alive interval.\n",
			 m_aliveint );
}

void
Match::cancel_claim_timer()
{
	if( m_claim_tid != -1 ) {
		daemonCore->Cancel_Timer( m_claim_tid );
		m_claim_tid = -1;
		dprintf( D_FULLDEBUG, "Canceled claim timer.\n" );
	}
}

int
Match::claim_timed_out()
{
	Resource* rip = resmgr->get_by_cur_cap( capab() );
	if( !rip ) {
		EXCEPT( "Can't find resource of expired claim." );
	}
		// Note that this claim timed out so we don't try to send a
		// command to our client.
	if( m_client ) {
		delete m_client;
		m_client = NULL;
	}
		// Release the claim.
	rip->release_claim();
	return TRUE;
}

void
Match::alive()
{
		// Process a keep alive command
	daemonCore->Reset_Timer( m_claim_tid, (3 * m_aliveint), 0 );
}

// Set our ad to the given pointer
void
Match::setad(ClassAd *ad)
{
	if( m_ad ) {
		delete( m_ad );
	}
	m_ad = ad;
}

void
Match::deletead(void)
{
	if( m_ad ) {
		delete( m_ad );
		m_ad = NULL;
	}
}

void
Match::setagentstream(Stream* stream)
{
	if( m_agentstream ) {
		delete( m_agentstream );
	}
	m_agentstream = stream;
}

///////////////////////////////////////////////////////////////////////////
// Client
///////////////////////////////////////////////////////////////////////////

Client::Client()
{
	c_name = NULL;
	c_addr = NULL;
	c_host = NULL;
}

Client::~Client()
{
	free( c_name );
	free( c_addr );
	free( c_host );
}

void
Client::setname(char* name)
{
	if( c_name ) {
		free( c_name);
	}
	if( name ) {
		c_name = strdup( name );
	} else {
		c_name = NULL;
	}
}

void
Client::setaddr(char* addr)
{
	if( c_addr ) {
		free( c_addr);
	}
	if( addr ) {
		c_addr = strdup( addr );
	} else {
		c_addr = NULL;
	}
}

void
Client::sethost(char* host)
{
	if( c_host ) {
		free( c_host);
	}
	if( host ) {
		c_host = strdup( host );
	} else {
		c_host = NULL;
	}
}

void
Client::vacate(char* cap)
{
	ReliSock sock;
	sock.timeout( 20 );

	if( ! (c_addr || c_host || c_name ) ) {
			// Client not really set, nothing to do.
		return;
	}

	dprintf(D_FULLDEBUG, "Entered vacate_client %s %s...\n", c_addr, c_host);

	if(	! sock.connect( c_addr, 0 ) ) {
		dprintf(D_ALWAYS, "Can't connect to schedd (%s)\n", c_addr);
	} else {
		if( !sock.put( RELEASE_CLAIM ) ) {
			dprintf(D_ALWAYS, "Can't send RELEASE_CLAIM command to client\n");
		} else if( !sock.put( cap ) ) {
			dprintf(D_ALWAYS, "Can't send capability to client\n");
		} else if( !sock.eom() ) {
			dprintf(D_ALWAYS, "Can't send EOM to client\n");
		}
		sock.close();
	}
}

///////////////////////////////////////////////////////////////////////////
// Capability
///////////////////////////////////////////////////////////////////////////

char*
new_capability_string()
{
	char cap[128];
	char tmp[128];
	char randbuf[12];
	randbuf[0] = '\0';
	int i, len;

		// Create a really mangled 10 digit random number: The first 6
		// digits are generated as follows: for the ith digit, pull
		// the ith digit off a new random int.  So our 1st slot comes
		// from the 1st digit of 1 random int, the 2nd from the 2nd
		// digit of a 2nd random it, etc...  If we're trying to get a
		// digit from a number that's too small to have that many, we
		// just use the last digit.  The last 4 digits of our number
		// come from the first 4 digits of the current time multiplied
		// by a final random int.  That should keep 'em guessing. :)
		// -Derek Wright 1/8/98
	for( i=0; i<6; i++ ) {
		sprintf( tmp, "%d", get_random_int() );
		len = strlen(tmp);
		if( i < len ) {
			tmp[i+1] = '\0';
			strcat( randbuf, tmp+i );
		} else {
			strcat( randbuf, tmp+(len-1) );
		}
	}
	sprintf( tmp, "%f", (double)((float)time(NULL) * (float)get_random_int()) );
	tmp[4]='\0';
	strcat( randbuf, tmp );

		// Capability string is "<ip:port>#random_number"
	strcpy( cap, daemonCore->InfoCommandSinfulString() );
	strcat( cap, "#" );
	strcat( cap, randbuf );
	return strdup( cap );
}

Capability::Capability()
{
	c_capab = new_capability_string();
}

Capability::~Capability()
{
	free( c_capab );
}

int
Capability::matches(char* capab)
{
	return( strcmp(capab, c_capab) == 0 );
}

<commit_msg>+ Added Match::refuse_agent() to send NOT_OK to the schedd agent that   is waiting for us to tell it if it can claim us or not. + In the Match object's destructor, we DO want to delete m_agentstream   if it exists, since once we told DaemonCore to KEEP_STREAM, it's   never going to touch that stream again (since we never register it   again with DC for anything).<commit_after>/*
	This file implements the classes defined in Match.h:

	Match, Capability, and Client

	A Match object contains all of the information a startd needs
    about a given match, such as the capability, the client of this
    match, etc.  The capability in the match is just a pointer to a
    Capability object.  The client is also just a pointer to a Client
    object.  The startd maintains two Match objects in the "rip", the
    per-resource information structure.  One for the current match,
    and one for the possibly preempting match that is pending.

	A Capability object contains the capability string, and some
	functions to manipulate and compare against this string.  The
	constructor generates a new capability with the following form:
	<ip:port>#random_integer

	A Client object contains all the info about a given client of a
	startd.  In particular, the client name (a.k.a. "user"), the
	address ("<www.xxx.yyy.zzz:pppp>" formed ip/port), and the
	hostname.

	Written 9/29/97 by Derek Wright <wright@cs.wisc.edu>
*/

#include "startd.h"
static char *_FileName_ = __FILE__;

///////////////////////////////////////////////////////////////////////////
// Match
///////////////////////////////////////////////////////////////////////////

Match::Match()
{
	m_client = new Client;
	m_cap = new Capability;
	m_ad = NULL;
	m_rank = -1;
	m_oldrank = -1;
	m_universe = -1;
	m_agentstream = NULL;
	m_match_tid = -1;
	m_claim_tid = -1;
	m_aliveint = -1;
}

Match::~Match()
{
		// Cancel any timers associated with this match
	this->cancel_match_timer();
	this->cancel_claim_timer();

		// Free up memory that's been allocated
	if( m_ad ) {
		delete( m_ad );
	}
	delete( m_cap );
	if( m_client ) {
		delete( m_client );
	}
	if( m_agentstream ) {
		delete( m_agentstream );
	}

}

void
Match::vacate()
{
	assert( m_cap );
		// warn the client of this match that it's being vacated
	if( m_client && m_client->addr() ) {
		m_client->vacate( m_cap->capab() );
	}
}

int
Match::send_accountant( int cmd )
{
	if( !accountant_host ) {
		return FALSE;
	}
	ReliSock sock;
	sock.timeout( 30 );
	if( ! sock.connect( accountant_host, ACCOUNTANT_PORT ) ) {
		dprintf(D_ALWAYS, "Couldn't connect to accountant\n");
		return FALSE;
	}
	if( !sock.put( cmd ) ) {
		dprintf(D_ALWAYS, "Can't send command (%d) to accountant\n", cmd );
		return FALSE;
	}
	if( !sock.put( m_cap->capab() ) ) {
		dprintf(D_ALWAYS, "Can't send capability to accountant\n");
		return FALSE;
	}
	if( !sock.eom() ) {
		dprintf(D_ALWAYS, "Can't send EOM to accountant\n");
		return FALSE;
	}
	sock.close();
	return TRUE;
}

void
Match::update( ClassAd* ad )
{
	char line[1024];
	char* tmp;

			// Update the current rank value
	sprintf( line, "%s = %f", ATTR_CURRENT_RANK, m_rank );
	ad->Insert( line );

	if( m_client ) {
		tmp = m_client->name();
		if( tmp ) {
			sprintf( line, "%s=\"%s\"", ATTR_REMOTE_USER, tmp );
			ad->Insert( line );
		}
		tmp = m_client->host();
		if( tmp ) {
			sprintf( line, "%s=\"%s\"", ATTR_CLIENT_MACHINE, tmp );
			ad->Insert( line );
		}
	}
}

void
Match::refuse_agent()
{
	if( !m_agentstream ) return;
	dprintf( D_ALWAYS, "Refusing request from schedd agent.\n" );
	m_agentstream->encode();
	m_agentstream->put(NOT_OK);
	m_agentstream->end_of_message();
}

void
Match::start_match_timer()
{
	m_match_tid =
		daemonCore->Register_Timer( match_timeout, 0,
								   (TimerHandlercpp)match_timed_out,
								   "match_timed_out", this );
	if( m_match_tid == -1 ) {
		EXCEPT( "Couldn't register timer (out of memory)." );
	}
	dprintf( D_FULLDEBUG, "Started match timer for %d seconds.\n",
			 match_timeout );
}

void
Match::cancel_match_timer()
{
	if( m_match_tid != -1 ) {
		daemonCore->Cancel_Timer( m_match_tid );
		m_match_tid = -1;
		dprintf( D_FULLDEBUG, "Canceled match timer.\n" );
	}
}

int
Match::match_timed_out()
{
	Resource* rip = resmgr->get_by_any_cap( capab() );
	if( !rip ) {
		EXCEPT( "Can't find resource of expired match." );
	}

	if( rip->r_cur->cap()->matches( capab() ) ) {
		if( rip->state() != matched_state ) {
			EXCEPT( "Match timed out but not in matched state." );
		}
		delete rip->r_cur;
		rip->r_cur = new Match;
		rip->change_state( owner_state );
	} else {
			// The match that timed out was the preempting match.
		assert( rip->r_pre->cap()->matches( capab() ) );
			// We need to generate a new preempting match object,
			// restore our reqexp, and update the CM.
		delete rip->r_pre;
		rip->r_pre = new Match;
		rip->r_reqexp->pub();
		rip->update();
	}
	return TRUE;
}

void
Match::start_claim_timer()
{
	if( m_aliveint < 0 ) {
		dprintf( D_ALWAYS,
				 "Warning: starting claim timer before alive interval set.\n" );
		m_aliveint = 300;
	}
	m_claim_tid =
		daemonCore->Register_Timer( (3 * m_aliveint), 0,
									(TimerHandlercpp)claim_timed_out,
									"claim_timed_out", this );
	if( m_claim_tid == -1 ) {
		EXCEPT( "Couldn't register timer (out of memory)." );
	}
	dprintf( D_FULLDEBUG, "Started claim timer w/ %d second alive interval.\n",
			 m_aliveint );
}

void
Match::cancel_claim_timer()
{
	if( m_claim_tid != -1 ) {
		daemonCore->Cancel_Timer( m_claim_tid );
		m_claim_tid = -1;
		dprintf( D_FULLDEBUG, "Canceled claim timer.\n" );
	}
}

int
Match::claim_timed_out()
{
	Resource* rip = resmgr->get_by_cur_cap( capab() );
	if( !rip ) {
		EXCEPT( "Can't find resource of expired claim." );
	}
		// Note that this claim timed out so we don't try to send a
		// command to our client.
	if( m_client ) {
		delete m_client;
		m_client = NULL;
	}
		// Release the claim.
	rip->release_claim();
	return TRUE;
}

void
Match::alive()
{
		// Process a keep alive command
	daemonCore->Reset_Timer( m_claim_tid, (3 * m_aliveint), 0 );
}

// Set our ad to the given pointer
void
Match::setad(ClassAd *ad)
{
	if( m_ad ) {
		delete( m_ad );
	}
	m_ad = ad;
}

void
Match::deletead(void)
{
	if( m_ad ) {
		delete( m_ad );
		m_ad = NULL;
	}
}

void
Match::setagentstream(Stream* stream)
{
	if( m_agentstream ) {
		delete( m_agentstream );
	}
	m_agentstream = stream;
}

///////////////////////////////////////////////////////////////////////////
// Client
///////////////////////////////////////////////////////////////////////////

Client::Client()
{
	c_name = NULL;
	c_addr = NULL;
	c_host = NULL;
}

Client::~Client()
{
	free( c_name );
	free( c_addr );
	free( c_host );
}

void
Client::setname(char* name)
{
	if( c_name ) {
		free( c_name);
	}
	if( name ) {
		c_name = strdup( name );
	} else {
		c_name = NULL;
	}
}

void
Client::setaddr(char* addr)
{
	if( c_addr ) {
		free( c_addr);
	}
	if( addr ) {
		c_addr = strdup( addr );
	} else {
		c_addr = NULL;
	}
}

void
Client::sethost(char* host)
{
	if( c_host ) {
		free( c_host);
	}
	if( host ) {
		c_host = strdup( host );
	} else {
		c_host = NULL;
	}
}

void
Client::vacate(char* cap)
{
	ReliSock sock;
	sock.timeout( 20 );

	if( ! (c_addr || c_host || c_name ) ) {
			// Client not really set, nothing to do.
		return;
	}

	dprintf(D_FULLDEBUG, "Entered vacate_client %s %s...\n", c_addr, c_host);

	if(	! sock.connect( c_addr, 0 ) ) {
		dprintf(D_ALWAYS, "Can't connect to schedd (%s)\n", c_addr);
	} else {
		if( !sock.put( RELEASE_CLAIM ) ) {
			dprintf(D_ALWAYS, "Can't send RELEASE_CLAIM command to client\n");
		} else if( !sock.put( cap ) ) {
			dprintf(D_ALWAYS, "Can't send capability to client\n");
		} else if( !sock.eom() ) {
			dprintf(D_ALWAYS, "Can't send EOM to client\n");
		}
		sock.close();
	}
}

///////////////////////////////////////////////////////////////////////////
// Capability
///////////////////////////////////////////////////////////////////////////

char*
new_capability_string()
{
	char cap[128];
	char tmp[128];
	char randbuf[12];
	randbuf[0] = '\0';
	int i, len;

		// Create a really mangled 10 digit random number: The first 6
		// digits are generated as follows: for the ith digit, pull
		// the ith digit off a new random int.  So our 1st slot comes
		// from the 1st digit of 1 random int, the 2nd from the 2nd
		// digit of a 2nd random it, etc...  If we're trying to get a
		// digit from a number that's too small to have that many, we
		// just use the last digit.  The last 4 digits of our number
		// come from the first 4 digits of the current time multiplied
		// by a final random int.  That should keep 'em guessing. :)
		// -Derek Wright 1/8/98
	for( i=0; i<6; i++ ) {
		sprintf( tmp, "%d", get_random_int() );
		len = strlen(tmp);
		if( i < len ) {
			tmp[i+1] = '\0';
			strcat( randbuf, tmp+i );
		} else {
			strcat( randbuf, tmp+(len-1) );
		}
	}
	sprintf( tmp, "%f", (double)((float)time(NULL) * (float)get_random_int()) );
	tmp[4]='\0';
	strcat( randbuf, tmp );

		// Capability string is "<ip:port>#random_number"
	strcpy( cap, daemonCore->InfoCommandSinfulString() );
	strcat( cap, "#" );
	strcat( cap, randbuf );
	return strdup( cap );
}

Capability::Capability()
{
	c_capab = new_capability_string();
}

Capability::~Capability()
{
	free( c_capab );
}

int
Capability::matches(char* capab)
{
	return( strcmp(capab, c_capab) == 0 );
}

<|endoftext|>


<commit_before>/***************************************************************
 *
 * Copyright (C) 1990-2020, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/

#include "condor_common.h"
#include "condor_config.h"
#include "match_prefix.h"
#include <termios.h>
#include <unistd.h>
#include <grp.h>
#include <signal.h>
#include <algorithm>

// condor_nsenter
//
// This is a replacement for the linux nsenter command to launch a
// shell inside a container.  We need this when running
// condor_ssh_to_job to a job that has been launched inside singularity
// Docker jobs use docker exec to enter the container, but there is no
// equivalent in singularity.  Standard nsenter isn't sufficient, as it
// does not create a pty for the shell, and we need different command
// line arguments to enter a non-setuid singularity container.  This
// is harder because the starter can't tell if singularity is setuid easily.
//
// The architecture for ssh-to-job to a contained job is to land in an sshd
// the starter forks *outside* the container.  This is important because we
// never want to assume anything about the container, even that there is an sshd
// inside it.  After the sshd starts, a script runs which runs condor_docker_enter
// (even for singularity), which connects to a Unix Domain Socket, passes
// stdin/out/err to the starter, which passes those to condor_nsenter, which
// is runs as root.  Rootly privilege is required to enter a setuid namespace
// so this is how we acquire.
//
// condor_nsenter enters the namespace, taking care to try to enter the user
// namespace, which is only set up for non-setuid singularity, and drops
// privileges to the uid and gid passed on the command line.  It sets up
// a pty for the shell to use, so all interactive processses will work.
//
// A final problem is environment variables.  Singularity, especially when
// managing GPUs, sets some nvidia-related environment variables that the
// condor starter doesn't know about.  So, condor_nsenter extracts the environment
// variables from the contained job process, and sets them in the shell
// it spawns.

void
usage( char *cmd )
{
	fprintf(stderr,"Usage: %s [options] condor_* ....\n",cmd);
	fprintf(stderr,"Where options are:\n");
	fprintf(stderr,"-t target_pid:\n");
	fprintf(stderr,"-S user_id:\n");
	fprintf(stderr,"-G group_id:\n");
	fprintf(stderr,"    -help              Display options\n");
}

#if (__GLIBC__ == 2) && (__GLIBC_MINOR__ < 15)
// el6 doesn't have setns.  What are we going to do?
// what can we do?  Just not enter the container
int setns(int , int) {
        return 0;
}
#endif

// Before we exit, we need to reset the pty back to normal
// if we put it in raw mode

bool pty_is_raw = false;
struct termios old_tio;
void reset_pty_and_exit(int signo) {
	if (pty_is_raw)
		tcsetattr(0, TCSAFLUSH, &old_tio);
	exit(signo);
}

int main( int argc, char *argv[] )
{
	std::string condor_prefix;
	pid_t pid = 0;
	uid_t uid = 0;
	gid_t gid = 0;

	// parse command line args
	for( int i=1; i<argc; i++ ) {
		if(is_arg_prefix(argv[i],"-help")) {
			usage(argv[0]);
			exit(1);
		}

		// target pid to enter
		if(is_arg_prefix(argv[i],"-t")) {
			pid = atoi(argv[i + 1]);
			i++;
		}

		// uid to switch to
		if(is_arg_prefix(argv[i],"-S")) {
			uid = atoi(argv[i + 1]);
			i++;
		}

		// gid to switch to
		if(is_arg_prefix(argv[i],"-G")) {
			gid = atoi(argv[i + 1]);
			i++;
		}
	}

	if (pid < 1) {
		fprintf(stderr, "missing -t argument > 1\n");
		exit(1);
	}

	if (uid == 0) {
		fprintf(stderr, "missing -S argument > 1\n");
		exit(1);
	}

	if (gid == 0) {
		fprintf(stderr, "missing -G argument > 1\n");
		exit(1);
	}

	// slurp the enviroment out of our victim
	std::string env;
	std::string envfile;
	formatstr(envfile, "/proc/%d/environ", pid);

	int e = open(envfile.c_str(), O_RDONLY);
	if (e < 0) {
		fprintf(stderr, "Can't open %s %s\n", envfile.c_str(), strerror(errno));
		exit(1);
	}
	char buf[512];
	int bytesRead;
	while ((bytesRead = read(e, &buf, 512)) > 0) {
		env.append(buf, bytesRead);
	}
	close(e);

	// If the environment contains a string with the value of the
	// path to the container's scratch directory outside the container
	// replace it with the path inside the container.

	std::string outsidePath;
	if (getenv("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER")) {
		outsidePath = getenv("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER");
	}
	std::string insidePath;
	if (getenv("_CONDOR_SCRATCH_DIR")) {
		insidePath = getenv("_CONDOR_SCRATCH_DIR");
	}
	if (outsidePath.length() > 0) {
		size_t pos = 0;
		do {
			pos = env.find(outsidePath, pos);
			if (pos != std::string::npos) {
				env.replace(pos, outsidePath.length(), insidePath);
				pos += insidePath.length();
			}
		} while (pos != std::string::npos);
	}
	// we probably replaced the value of _CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER
	// with the inside value, so set that back
	size_t pos = 0;
	pos = env.find("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER=", pos);
	if (pos != std::string::npos) {
		size_t eqpos = env.find("=", pos);
		eqpos++;
		env.replace(eqpos, insidePath.length(), outsidePath);
	}

	// make a vector to hold all the pointers to env entries
	std::vector<const char *> envp;

	// the first one
	envp.push_back(env.c_str());
	auto it = env.begin();
	while (env.end() != (it = std::find(it, env.end(), '\0'))) {
		// skip past null terminator
		it++;
		if (& (*it)  != 0) {
			envp.push_back(& (*it));
		}
	}
	envp.push_back(0);
	envp.push_back(0);

	std::string filename;

	// start changing namespaces.  Note that once we do this, things
	// get funny in this process
	formatstr(filename, "/proc/%d/ns/uts", pid);
	int fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open uts namespace: %d %s\n", errno, strerror(errno));
		exit(1);
	}
	int r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		// This means an unprivileged singularity, most likely
		// need to set user namespace instead.
		formatstr(filename, "/proc/%d/ns/user", pid);
		fd = open(filename.c_str(), O_RDONLY);
		if (fd < 0) {
			fprintf(stderr, "Can't open user namespace: %d %s\n", errno, strerror(errno));
			exit(1);
		}
		r = setns(fd, 0);
		close(fd);
		if (r < 0) {
			fprintf(stderr, "Can't setns to user namespace: %s\n", strerror(errno));
			exit(1);
		}
	}

	// now the pid namespace
	formatstr(filename, "/proc/%d/ns/pid", pid);
	fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open pid namespace: %s\n", strerror(errno));
		exit(1);
	}
	r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		fprintf(stderr, "Can't setns to pid namespace: %s\n", strerror(errno));
		exit(1);
	}

	// finally the mnt namespace
	formatstr(filename, "/proc/%d/ns/mnt", pid);
	fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open mnt namespace: %s\n", strerror(errno));
		exit(1);
	}
	r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		fprintf(stderr, "Can't setns to mnt namespace: %s\n", strerror(errno));
		exit(1);
	}

	setgroups(0, 0);

	// order matters!
	r = setgid(gid);
	if (r < 0) {
		fprintf(stderr, "Can't setgid to %d\n", gid);
		exit(1);
	}
	r = setuid(uid);
	if (r < 0) {
		fprintf(stderr, "Can't setuid to %d\n", uid);
		exit(1);
	}

	struct winsize win;
	ioctl(0, TIOCGWINSZ, &win);

	// now the pty handling
	int masterPty = -1;
	int workerPty = -1;
	masterPty = open("/dev/ptmx", O_RDWR);
	unlockpt(masterPty);

	if (masterPty < 0) {
		fprintf(stderr, "Can't open master pty %s\n", strerror(errno));
		exit(1);
	} else {
		workerPty = open(ptsname(masterPty), O_RDWR);
		if (workerPty < 0) {
			fprintf(stderr, "Can't open worker pty %s\n", strerror(errno));
			exit(1);
		}
	}
	int childpid = fork();
	if (childpid == 0) {

		// in the child --

		close(0);
		close(1);
		close(2);
		close(masterPty);

		dup2(workerPty, 0);
		dup2(workerPty, 1);
		dup2(workerPty, 2);

		if (getenv("_CONDOR_SCRATCH_DIR")) {
			int r = chdir(getenv("_CONDOR_SCRATCH_DIR"));
			if (r < 0) {
				printf("Cannot chdir to %s: %s\n", getenv("_CONDOR_SCRATCH_DIR"), strerror(errno));
			}
 		}

		// make this process group leader so shell job control works
		setsid();

		// Make the pty the controlling terminal
		ioctl(workerPty, TIOCSCTTY, 0);

		// and make it the process group leader
		tcsetpgrp(workerPty, getpid());

		// and set the window size properly
		ioctl(0, TIOCSWINSZ, &win);

		// Finally, launch the shell
		execle("/bin/sh", "/bin/sh", "-l", "-i", 0, envp.data());

		// Only get here if exec fails
		fprintf(stderr, "exec failed %d\n", errno);
		exit(errno);

	} else {

		// the parent
		fd_set readfds, writefds, exceptfds;
		bool keepGoing = true;

		// put the pty in raw mode
		struct termios tio;
		tcgetattr(0, &tio);
		pty_is_raw = true;
		old_tio = tio;
		tio.c_oflag &= ~(OPOST);
		tio.c_cflag |= (CS8);
		tio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
		tio.c_cc[VMIN] = 1;
		tio.c_cc[VTIME] = 0;
		tcsetattr(0, TCSAFLUSH, &tio);

		struct sigaction handler;
		struct sigaction oldhandler;
		handler.sa_handler = reset_pty_and_exit;
		handler.sa_flags   = 0;

		sigaction(SIGCHLD, &handler, &oldhandler);

		while (keepGoing) {
			FD_ZERO(&readfds);
			FD_ZERO(&writefds);
			FD_ZERO(&exceptfds);

			FD_SET(0, &readfds);
			FD_SET(masterPty, &readfds);

			select(masterPty + 1, &readfds, &writefds, &exceptfds, 0);

			if (FD_ISSET(masterPty, &readfds)) {
				char buf;
				int r = read(masterPty, &buf, 1);
				if (r > 0) {
					int ret = write(1, &buf, 1);
					if (ret < 0) {
						reset_pty_and_exit(0);
					}
				} else {
					keepGoing = false;
				}
			}

			if (FD_ISSET(0, &readfds)) {
				char buf;
				int r = read(0, &buf, 1);
				if (r > 0) {
					int ret = write(masterPty, &buf, 1);
					if (ret < 0) {
						reset_pty_and_exit(0);
					}
				} else {
					keepGoing = false;
				}
			}
		}
		int status;
		waitpid(childpid, &status, 0);
	}
	return 0;
}

<commit_msg>Copy DISPLAY var from outside ssh-to-job to inside #7666<commit_after>/***************************************************************
 *
 * Copyright (C) 1990-2020, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/

#include "condor_common.h"
#include "condor_config.h"
#include "match_prefix.h"
#include <termios.h>
#include <unistd.h>
#include <grp.h>
#include <signal.h>
#include <algorithm>

// condor_nsenter
//
// This is a replacement for the linux nsenter command to launch a
// shell inside a container.  We need this when running
// condor_ssh_to_job to a job that has been launched inside singularity
// Docker jobs use docker exec to enter the container, but there is no
// equivalent in singularity.  Standard nsenter isn't sufficient, as it
// does not create a pty for the shell, and we need different command
// line arguments to enter a non-setuid singularity container.  This
// is harder because the starter can't tell if singularity is setuid easily.
//
// The architecture for ssh-to-job to a contained job is to land in an sshd
// the starter forks *outside* the container.  This is important because we
// never want to assume anything about the container, even that there is an sshd
// inside it.  After the sshd starts, a script runs which runs condor_docker_enter
// (even for singularity), which connects to a Unix Domain Socket, passes
// stdin/out/err to the starter, which passes those to condor_nsenter, which
// is runs as root.  Rootly privilege is required to enter a setuid namespace
// so this is how we acquire.
//
// condor_nsenter enters the namespace, taking care to try to enter the user
// namespace, which is only set up for non-setuid singularity, and drops
// privileges to the uid and gid passed on the command line.  It sets up
// a pty for the shell to use, so all interactive processses will work.
//
// A final problem is environment variables.  Singularity, especially when
// managing GPUs, sets some nvidia-related environment variables that the
// condor starter doesn't know about.  So, condor_nsenter extracts the environment
// variables from the contained job process, and sets them in the shell
// it spawns.

void
usage( char *cmd )
{
	fprintf(stderr,"Usage: %s [options] condor_* ....\n",cmd);
	fprintf(stderr,"Where options are:\n");
	fprintf(stderr,"-t target_pid:\n");
	fprintf(stderr,"-S user_id:\n");
	fprintf(stderr,"-G group_id:\n");
	fprintf(stderr,"    -help              Display options\n");
}

#if (__GLIBC__ == 2) && (__GLIBC_MINOR__ < 15)
// el6 doesn't have setns.  What are we going to do?
// what can we do?  Just not enter the container
int setns(int , int) {
        return 0;
}
#endif

// Before we exit, we need to reset the pty back to normal
// if we put it in raw mode

bool pty_is_raw = false;
struct termios old_tio;
void reset_pty_and_exit(int signo) {
	if (pty_is_raw)
		tcsetattr(0, TCSAFLUSH, &old_tio);
	exit(signo);
}

int main( int argc, char *argv[] )
{
	std::string condor_prefix;
	pid_t pid = 0;
	uid_t uid = 0;
	gid_t gid = 0;

	// parse command line args
	for( int i=1; i<argc; i++ ) {
		if(is_arg_prefix(argv[i],"-help")) {
			usage(argv[0]);
			exit(1);
		}

		// target pid to enter
		if(is_arg_prefix(argv[i],"-t")) {
			pid = atoi(argv[i + 1]);
			i++;
		}

		// uid to switch to
		if(is_arg_prefix(argv[i],"-S")) {
			uid = atoi(argv[i + 1]);
			i++;
		}

		// gid to switch to
		if(is_arg_prefix(argv[i],"-G")) {
			gid = atoi(argv[i + 1]);
			i++;
		}
	}

	if (pid < 1) {
		fprintf(stderr, "missing -t argument > 1\n");
		exit(1);
	}

	if (uid == 0) {
		fprintf(stderr, "missing -S argument > 1\n");
		exit(1);
	}

	if (gid == 0) {
		fprintf(stderr, "missing -G argument > 1\n");
		exit(1);
	}

	// slurp the enviroment out of our victim
	std::string env;
	std::string envfile;
	formatstr(envfile, "/proc/%d/environ", pid);

	int e = open(envfile.c_str(), O_RDONLY);
	if (e < 0) {
		fprintf(stderr, "Can't open %s %s\n", envfile.c_str(), strerror(errno));
		exit(1);
	}
	char buf[512];
	int bytesRead;
	while ((bytesRead = read(e, &buf, 512)) > 0) {
		env.append(buf, bytesRead);
	}
	close(e);

	// If the environment contains a string with the value of the
	// path to the container's scratch directory outside the container
	// replace it with the path inside the container.

	std::string outsidePath;
	if (getenv("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER")) {
		outsidePath = getenv("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER");
	}
	std::string insidePath;
	if (getenv("_CONDOR_SCRATCH_DIR")) {
		insidePath = getenv("_CONDOR_SCRATCH_DIR");
	}
	if (outsidePath.length() > 0) {
		size_t pos = 0;
		do {
			pos = env.find(outsidePath, pos);
			if (pos != std::string::npos) {
				env.replace(pos, outsidePath.length(), insidePath);
				pos += insidePath.length();
			}
		} while (pos != std::string::npos);
	}
	// we probably replaced the value of _CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER
	// with the inside value, so set that back
	size_t pos = 0;
	pos = env.find("_CONDOR_SCRATCH_DIR_OUTSIDE_CONTAINER=", pos);
	if (pos != std::string::npos) {
		size_t eqpos = env.find("=", pos);
		eqpos++;
		env.replace(eqpos, insidePath.length(), outsidePath);
	}

	// make a vector to hold all the pointers to env entries
	std::vector<const char *> envp;

	// copy DISPLAY from outside to inside.  sshd has set this,
	// it is the only one from the outside we need on the inside,
	// and one way that an ssh-to-job shell is different than the job.
	std::string display;
	if (getenv("DISPLAY")) {
		formatstr(display, "DISPLAY=%s", getenv("DISPLAY"));
		envp.push_back(display.c_str());
	}

	// the first one
	envp.push_back(env.c_str());
	auto it = env.begin();
	while (env.end() != (it = std::find(it, env.end(), '\0'))) {
		// skip past null terminator
		it++;
		if (& (*it)  != 0) {
			envp.push_back(& (*it));
		}
	}
	envp.push_back(0);
	envp.push_back(0);

	std::string filename;

	// start changing namespaces.  Note that once we do this, things
	// get funny in this process
	formatstr(filename, "/proc/%d/ns/uts", pid);
	int fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open uts namespace: %d %s\n", errno, strerror(errno));
		exit(1);
	}
	int r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		// This means an unprivileged singularity, most likely
		// need to set user namespace instead.
		formatstr(filename, "/proc/%d/ns/user", pid);
		fd = open(filename.c_str(), O_RDONLY);
		if (fd < 0) {
			fprintf(stderr, "Can't open user namespace: %d %s\n", errno, strerror(errno));
			exit(1);
		}
		r = setns(fd, 0);
		close(fd);
		if (r < 0) {
			fprintf(stderr, "Can't setns to user namespace: %s\n", strerror(errno));
			exit(1);
		}
	}

	// now the pid namespace
	formatstr(filename, "/proc/%d/ns/pid", pid);
	fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open pid namespace: %s\n", strerror(errno));
		exit(1);
	}
	r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		fprintf(stderr, "Can't setns to pid namespace: %s\n", strerror(errno));
		exit(1);
	}

	// finally the mnt namespace
	formatstr(filename, "/proc/%d/ns/mnt", pid);
	fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open mnt namespace: %s\n", strerror(errno));
		exit(1);
	}
	r = setns(fd, 0);
	close(fd);
	if (r < 0) {
		fprintf(stderr, "Can't setns to mnt namespace: %s\n", strerror(errno));
		exit(1);
	}

	setgroups(0, 0);

	// order matters!
	r = setgid(gid);
	if (r < 0) {
		fprintf(stderr, "Can't setgid to %d\n", gid);
		exit(1);
	}
	r = setuid(uid);
	if (r < 0) {
		fprintf(stderr, "Can't setuid to %d\n", uid);
		exit(1);
	}

	struct winsize win;
	ioctl(0, TIOCGWINSZ, &win);

	// now the pty handling
	int masterPty = -1;
	int workerPty = -1;
	masterPty = open("/dev/ptmx", O_RDWR);
	unlockpt(masterPty);

	if (masterPty < 0) {
		fprintf(stderr, "Can't open master pty %s\n", strerror(errno));
		exit(1);
	} else {
		workerPty = open(ptsname(masterPty), O_RDWR);
		if (workerPty < 0) {
			fprintf(stderr, "Can't open worker pty %s\n", strerror(errno));
			exit(1);
		}
	}
	int childpid = fork();
	if (childpid == 0) {

		// in the child --

		close(0);
		close(1);
		close(2);
		close(masterPty);

		dup2(workerPty, 0);
		dup2(workerPty, 1);
		dup2(workerPty, 2);

		if (getenv("_CONDOR_SCRATCH_DIR")) {
			int r = chdir(getenv("_CONDOR_SCRATCH_DIR"));
			if (r < 0) {
				printf("Cannot chdir to %s: %s\n", getenv("_CONDOR_SCRATCH_DIR"), strerror(errno));
			}
 		}

		// make this process group leader so shell job control works
		setsid();

		// Make the pty the controlling terminal
		ioctl(workerPty, TIOCSCTTY, 0);

		// and make it the process group leader
		tcsetpgrp(workerPty, getpid());

		// and set the window size properly
		ioctl(0, TIOCSWINSZ, &win);

		// Finally, launch the shell
		execle("/bin/sh", "/bin/sh", "-l", "-i", 0, envp.data());

		// Only get here if exec fails
		fprintf(stderr, "exec failed %d\n", errno);
		exit(errno);

	} else {

		// the parent
		fd_set readfds, writefds, exceptfds;
		bool keepGoing = true;

		// put the pty in raw mode
		struct termios tio;
		tcgetattr(0, &tio);
		pty_is_raw = true;
		old_tio = tio;
		tio.c_oflag &= ~(OPOST);
		tio.c_cflag |= (CS8);
		tio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
		tio.c_cc[VMIN] = 1;
		tio.c_cc[VTIME] = 0;
		tcsetattr(0, TCSAFLUSH, &tio);

		struct sigaction handler;
		struct sigaction oldhandler;
		handler.sa_handler = reset_pty_and_exit;
		handler.sa_flags   = 0;

		sigaction(SIGCHLD, &handler, &oldhandler);

		while (keepGoing) {
			FD_ZERO(&readfds);
			FD_ZERO(&writefds);
			FD_ZERO(&exceptfds);

			FD_SET(0, &readfds);
			FD_SET(masterPty, &readfds);

			select(masterPty + 1, &readfds, &writefds, &exceptfds, 0);

			if (FD_ISSET(masterPty, &readfds)) {
				char buf;
				int r = read(masterPty, &buf, 1);
				if (r > 0) {
					int ret = write(1, &buf, 1);
					if (ret < 0) {
						reset_pty_and_exit(0);
					}
				} else {
					keepGoing = false;
				}
			}

			if (FD_ISSET(0, &readfds)) {
				char buf;
				int r = read(0, &buf, 1);
				if (r > 0) {
					int ret = write(masterPty, &buf, 1);
					if (ret < 0) {
						reset_pty_and_exit(0);
					}
				} else {
					keepGoing = false;
				}
			}
		}
		int status;
		waitpid(childpid, &status, 0);
	}
	return 0;
}

<|endoftext|>