#include <iostream> #include <string> #include <vector> #include <getopt.h> #include <libgen.h> #include <cctype> #include <algorithm> #include <fstream> #include <iterator> #include <sstream> #include <set> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> using namespace std; // Parse some options for the transform static struct option long_options[] = { {"setup", no_argument, 0, 's'}, {"install_dir", required_argument, 0, 'i'}, {"manifest", required_argument, 0, 'm'}, {"clean", no_argument, 0, 'c'}, {"label", required_argument, 0, 'l'}, {"verbose", no_argument, 0, 'v'}, {"quiet", no_argument, 0, 'q'}, {"help", no_argument, 0, 'h'}, {"usage", no_argument, 0, '?'}, {0,0,0,0} }; static const auto short_opts="si:m:cl:vqh?"; void usage(int argc, char* argv[]) { cerr<<""<<argv[0]<<endl; cerr<<" ( --setup -m manifest.txt -i /path/to/installation -l label1 [-l label2 ...] | "<<endl; cerr<<" -m manifest.txt | "<<endl; cerr<<" -m manifest.txt -i /path/to/installation -l label1 [-l label2 ...] )"<<endl; cerr<<"options are:"<<endl; cerr<<" --setup,-s Create manifest.txt.config files, but don't install."<<endl; cerr<<" --clean,-c Remove any .config files."<<endl; cerr<<" --install_dir,-i Specify a location to install files listed in the manifest."<<endl; cerr<<" --manifest,-m Specify manifest.txt.config files to use."<<endl; cerr<<" --label,-l Specify labels to install."<<endl; cerr<<" --verbose,-v Spew debug data."<<endl; cerr<<" --quiet,-q No output."<<endl; cerr<<" --help,--usage,-h,-? This information."<<endl; } typedef struct PEDI_options PEDI_options_t; struct PEDI_options { string manifest_files; string labels; string install_dir; bool setup_only; bool clean; bool verbose; bool quiet; PEDI_options() : setup_only(false), clean(false), verbose(false) { } }; auto my_options=PEDI_options_t(); void parse_args(int argc, char* argv[]) { while(1) { auto index = 0; auto c = getopt_long(argc, argv,short_opts, long_options, &index); if(c == -1) break; switch(c) { case 0: break; case 's': my_options.setup_only=true; break; case 'c': my_options.clean=true; break; case 'i': if(my_options.install_dir!="") { cerr<<"Install dir cannot be specified multiple times"<<endl; exit(1); } my_options.install_dir=string(optarg); break; case 'm': my_options.manifest_files+=string(" ")+optarg; break; case 'l': my_options.labels+=string(" ")+optarg; break; case 'v': my_options.verbose=true; break; case 'q': my_options.quiet=true; break; case 'h': case '?': usage(argc,argv); exit(1); break; } } if(my_options.manifest_files=="") { cerr<<"Must specify a manifest file."<<endl; exit(1); } if(my_options.setup_only) { if(my_options.install_dir=="") { cerr<<"Must specify an install path with --setup."<<endl; exit(1); } if(my_options.labels=="") { cerr<<"Must specify labels to install with--setup."<<endl; exit(1); } } else { if(my_options.install_dir=="" && my_options.labels!="") { cerr<<"Cannot set install dir without setting labels."<<endl; exit(1); } if(my_options.install_dir!="" && my_options.labels=="") { cerr<<"Cannot set labels without setting install dir."<<endl; exit(1); } } } // trim from start (in place) static inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); } // trim from end (in place) static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); } // trim from both ends (in place) static inline void trim(std::string &s) { ltrim(s); rtrim(s); } // trim from start (copying) static inline std::string ltrimmed(std::string s) { ltrim(s); return s; } // trim from end (copying) static inline std::string rtrimmed(std::string s) { rtrim(s); return s; } // trim from both ends (copying) static inline std::string trimmed(std::string s) { trim(s); return s; } vector<string> split(const char *str, char c = ' ') { auto result=vector<string>(); do { const auto begin = str; while(*str != c && *str) str++; auto token=string(begin, str); if(trimmed(token)!="") result.push_back(token); } while (0 != *str++); return result; } vector<string> manifest_files; set<pair<string,string>> install_files; void include_file(const string &file, const string &label) { auto rp=realpath(trimmed(file).c_str(),NULL); if(rp==NULL) { cerr<<"Cannot find path to '"<<file<<"'"<<endl; perror("include_file"); exit(1); } auto rp_str=string(rp); // free the realpath allocation free(rp); // realpath with NULL install_files.insert(pair<string,string>(rp_str,trimmed(label))); if(my_options.verbose) { cout<<"found file ='"<<rp_str<<"', label='"<<trimmed(label)<<"'"<<endl; } } void include_dir(const int line_no, const string base_dir, const string sub_dir, const string label, bool recursive=false) { DIR *dir=NULL; struct dirent *ent=NULL; const auto dir_to_open=base_dir+"/"+sub_dir; if ((dir = opendir(dir_to_open.c_str())) != NULL) { /* print all the files and directories within directory */ while ((ent = readdir (dir)) != NULL) { // skip . and .. if(string(".")==ent->d_name || string("..")==ent->d_name) continue; const auto full_filename=dir_to_open+"/"+ent->d_name; const auto full_subdir_name= sub_dir+"/"+ent->d_name; if(ent->d_type==DT_REG) { include_file(full_filename, label); } else if(ent->d_type==DT_DIR) { if(recursive) include_dir(line_no, base_dir, full_subdir_name, label, recursive); } else if(ent->d_type==DT_UNKNOWN) { /* ok, resort to actually stat-ing the file */ struct stat fileinfo={0}; if(stat(full_filename.c_str(), &fileinfo)!=0) { cerr<<"Cannot open file: "<<full_filename<<endl; perror(""); exit(1); } if(S_ISREG(fileinfo.st_mode)) include_file(full_filename, label); else if(S_ISDIR(fileinfo.st_mode)) { if(recursive) include_dir(line_no, base_dir, full_subdir_name, label, recursive); } } } closedir (dir); } else { /* could not open directory */ cerr<<"At line "<<line_no<<": "<<" Cannot open "<<dir_to_open<<endl; perror (""); exit(1); } } void find_manifest_file(string &file) { /* caution! this useage of realpath allocates memory */ auto rp=realpath(trimmed(file).c_str(),NULL); if(rp==NULL) { cerr<<"Cannot find path to "<<file<<endl; exit(1); } auto rp_str=string(rp); /* caution! dirname modifies it's argument. */ auto dn=dirname(rp); auto dn_str=string(dn); // free the realpath allocation free(rp); // realpath with NULL file=rp_str; // update manifest_files vector with full path to manifest file // parse the manifest briefly, looking for submanifests. // note: can't use auto input= format due to no copy constructor for this class. ifstream input(rp_str); if(!input.is_open()) { cerr<<"Cannot open manifest file: "<<rp_str<<endl; exit(1); } auto line_no=0; for( auto line=string() ; getline(input,line); ) { auto tokens=split(trimmed(line).c_str(),' '); if(tokens.size() == 0) { /* ignore */ } else if(tokens[0]=="submanifest") { if(tokens.size() != 2) { cerr<<"Too many/few tokens for 'manifest' directive on line "<<line_no<<endl; cerr<<"line='"<<line<<"'"<<endl; exit(1); } manifest_files.push_back(dn_str+"/"+tokens[1]); } else if(tokens[0]=="file") { if(tokens.size() != 3) { cerr<<"Too many/few tokens for 'file' directive on line "<<line_no<<endl; cerr<<"line='"<<line<<"'"<<endl; exit(1); } if(my_options.clean || my_options.setup_only) { // do nothing } else { include_file(dn_str+"/"+tokens[1], tokens[2]); } } else if(tokens[0]=="recursive_directory") { if(my_options.clean || my_options.setup_only) { // do nothing } else { if(tokens.size() != 3) { cerr<<"Too many/few tokens for 'recursive_directory' directive on line "<<line_no<<endl; cerr<<"line='"<<line<<"'"<<endl; exit(1); } include_dir(line_no, dn_str, tokens[1],tokens[2], true); } } else if(tokens[0]=="directory") { if(my_options.clean || my_options.setup_only) { // do nothing } else { if(tokens.size() != 3) { cerr<<"Too many/few tokens for 'directory' directive on line "<<line_no<<endl; cerr<<"line='"<<line<<"'"<<endl; exit(1); } include_dir(line_no, dn_str, tokens[1], tokens[2], false); } } else if(tokens[0]=="" || tokens[0][0]=='#') { // ignore } else { cerr<<"At line "<<line_no<<": Syntax error at token '"<<tokens[0]<<"'."<<endl; exit(1); } line_no++; } } string manifest_prefix; void calc_top_manifest_prefix() { auto dn=strdup(manifest_files[0].c_str()); auto prefix_str=dirname(dn); manifest_prefix=string(prefix_str); free(dn); } void setup() { // split up the input string and push each one into the vector of all manifest files. // note: cannot use auto style initilizer because of deleted copy constructor. //auto mf=istringstream(my_options.manifest_files); istringstream mf(my_options.manifest_files); manifest_files.insert(end(manifest_files), istream_iterator<string>(mf), istream_iterator<string>()); find_manifest_file(manifest_files[0]); calc_top_manifest_prefix(); for(int i=1;i<manifest_files.size();i++) { find_manifest_file(manifest_files[i]); } } void clean() { for(const auto & s : manifest_files) { auto configfile=trimmed(s)+".config"; struct stat sb; auto stat_res=stat(configfile.c_str(), &sb); if(stat_res==0) { if(my_options.verbose) cout<<"Unlink "<<configfile<<endl; auto res=unlink(configfile.c_str()); if(res!=0) { cerr<<"Cannot unlink file: "<<configfile<<endl; perror(""); exit(1); } } } } void update_configs() { // only write the configs if updated labels are used. if(my_options.labels=="") return; for(const auto & s : manifest_files) { auto configfile=trimmed(s)+".config"; ofstream out(configfile, ofstream::out | ofstream::trunc); if(!out.is_open()) { cerr<<"Cannot open config file: "<<configfile<<endl; exit(1); } auto install_dir=s; install_dir.erase(0,manifest_prefix.size()); install_dir=my_options.install_dir+string("/")+install_dir; auto dn=strdup(install_dir.c_str()); auto dn_dir=dirname(dn); auto dn_dir_str=string(dn_dir); free(dn); out<<dn_dir_str<<endl; out<<my_options.labels<<endl; } } void read_config(const string &configfile, string &install_dir, string &labels) { ifstream in(configfile); if(!in.is_open()) { cerr<<"Cannot open config file: "<<configfile<<endl; exit(1); } if(!getline(in,install_dir)) { cerr<<"Cannot read "<<configfile<<endl; exit(1); } if(!getline(in,labels)) { cerr<<"Cannot read "<<configfile<<endl; exit(1); } } auto configs_read=false; void read_configs() { // only read the configs if no labels are specified. if(my_options.labels!="") return; configs_read=true; const auto mf=manifest_files[0]; auto orig_configfile=trimmed(mf)+".config"; read_config(orig_configfile, my_options.install_dir, my_options.labels); #if 0 for(const auto & s : manifest_files) { string install_dir, labels; auto configfile=trimmed(s)+".config"; read_config(configfile, install_dir, labels); if(my_options.install_dir != install_dir || my_options.labels != labels) { cerr<<"Mismatch configuration in "<<orig_configfile<<" and "<<configfile<<endl; exit(1); } } #endif } auto previously_had_pedi_root=false; void remove_stale() { struct stat sb; auto pedi_root_fn=my_options.install_dir+"/.pedi_root"; auto stat_res=stat(pedi_root_fn.c_str(), &sb); if(stat_res==0) { if(my_options.verbose) cout<<"Detected install dir as pedi_root install location."<<endl; previously_had_pedi_root=true; } auto cmd=string("/bin/rm -Rf /"+my_options.install_dir); auto res=system(cmd.c_str()); if(res!=0) { cerr<<"Cannot remove install directory: "<<my_options.install_dir<<endl; exit(1); } } void create_install_dir(const string &file) { auto tokens=split(file.c_str(), '/'); string full_path=""; tokens.pop_back(); for(const auto &s : tokens) { full_path+=string("/")+s; mkdir(full_path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH); } } void install() { const auto mf=manifest_files[0]; if(install_files.size()==0) { cerr<<"PEDI: No files to install! (did you remember --label)?"<<endl; exit(0); } auto installed_files=0; for(const auto & ifp : install_files) { const auto &file=ifp.first; const auto &label=ifp.second; istringstream labels(my_options.labels); auto found=find_if(istream_iterator<string>(labels), istream_iterator<string>(), [&](const string & word) { return trimmed(word)==trimmed(label); }); if(found==istream_iterator<string>()) { if(my_options.verbose) { cout<<"Eliding file='"<<file<<"', label='"<<label<<"'"<<endl; } } else { string outfile=file; outfile.erase(0,manifest_prefix.size()); outfile=my_options.install_dir+"/"+outfile; if(my_options.verbose) cout<<"Installing file "<<file<<" to " <<outfile<<endl; installed_files++; struct stat stat_buf; auto res=stat(file.c_str(),&stat_buf); if(res!=0) { cerr<<"Error acessing file: "<<file<<endl; perror(""); exit(1); } create_install_dir(outfile); ifstream src(file, ios::binary); ofstream dst(outfile, ios::binary); dst << src.rdbuf(); dst.close(); auto res2=chmod(outfile.c_str(), stat_buf.st_mode); if(res2!=0) { cerr<<"Error acessing file: "<<outfile<<endl; perror(""); exit(1); } } } if(!my_options.quiet) cout<<"PEDI: Installed "<<installed_files<<" files."<<endl; } void finish() { if(!configs_read || previously_had_pedi_root) { ofstream o(my_options.install_dir+"/"+".pedi_root"); if(my_options.verbose) cout<<"Writing pedi_root file."<<endl; return; } if(my_options.verbose) cout<<"Skipping pedi_root as partial install"<<endl; } int main(int argc, char* argv[]) { parse_args(argc,argv); if(!my_options.quiet) cout<<"Welcome to the Program Executable and Dependency Installer (PEDI) tool"<<endl; setup(); if(my_options.clean) { clean(); if(!my_options.quiet) cout<<"PEDI: Cleanup complete!"<<endl; return 0; } update_configs(); read_configs(); // finished after setup. if(my_options.setup_only) { finish(); return 0; } if(my_options.verbose) { cout<<"Installing files that match labels='"<<my_options.labels<<"' to "<<my_options.install_dir<<endl; } remove_stale(); install(); finish(); if(!my_options.quiet) cout<<"PEDI: Install complete!"<<endl; return 0; }