1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <unistd.h> 5#include <stdarg.h> 6#include "options.h" 7#include "files.h" 8#include "fs.h" 9#include <set> 10#include <iostream> 11#include <sstream> 12 13using namespace std; 14 15bool g_debug = getenv("ATREE_DEBUG") != NULL; 16vector<string> g_listFiles; 17vector<string> g_inputBases; 18map<string, string> g_variables; 19string g_outputBase; 20string g_dependency; 21bool g_useHardLinks = false; 22 23const char* USAGE = 24"\n" 25"Usage: atree OPTIONS\n" 26"\n" 27"Options:\n" 28" -f FILELIST Specify one or more files containing the\n" 29" list of files to copy.\n" 30" -I INPUTDIR Specify one or more base directories in\n" 31" which to look for the files\n" 32" -o OUTPUTDIR Specify the directory to copy all of the\n" 33" output files to.\n" 34" -l Use hard links instead of copying the files.\n" 35" -m DEPENDENCY Output a make-formatted file containing the list.\n" 36" of files included. It sets the variable ATREE_FILES.\n" 37" -v VAR=VAL Replaces ${VAR} by VAL when reading input files.\n" 38" -d Verbose debug mode.\n" 39"\n" 40"FILELIST file format:\n" 41" The FILELIST files contain the list of files that will end up\n" 42" in the final OUTPUTDIR. Atree will look for files in the INPUTDIR\n" 43" directories in the order they are specified.\n" 44"\n" 45" In a FILELIST file, comment lines start with a #. Other lines\n" 46" are of the format:\n" 47"\n" 48" [rm|strip] DEST\n" 49" SRC [strip] DEST\n" 50" -SRCPATTERN\n" 51"\n" 52" DEST should be path relative to the output directory.\n" 53" 'rm DEST' removes the destination file and fails if it's missing.\n" 54" 'strip DEST' strips the binary destination file.\n" 55" If SRC is supplied, the file names can be different.\n" 56" SRCPATTERN is a pattern for the filenames.\n" 57"\n"; 58 59int usage() 60{ 61 fwrite(USAGE, strlen(USAGE), 1, stderr); 62 return 1; 63} 64 65static bool 66add_variable(const char* arg) { 67 const char* p = arg; 68 while (*p && *p != '=') p++; 69 70 if (*p == 0 || p == arg || p[1] == 0) { 71 return false; 72 } 73 74 ostringstream var; 75 var << "${" << string(arg, p-arg) << "}"; 76 g_variables[var.str()] = string(p+1); 77 return true; 78} 79 80static void 81debug_printf(const char* format, ...) 82{ 83 if (g_debug) { 84 fflush(stderr); 85 va_list ap; 86 va_start(ap, format); 87 vprintf(format, ap); 88 va_end(ap); 89 fflush(stdout); 90 } 91} 92 93// Escape the filename so that it can be added to the makefile properly. 94static string 95escape_filename(const string name) 96{ 97 ostringstream new_name; 98 for (string::const_iterator iter = name.begin(); iter != name.end(); ++iter) 99 { 100 switch (*iter) 101 { 102 case '$': 103 new_name << "$$"; 104 break; 105 default: 106 new_name << *iter; 107 break; 108 } 109 } 110 return new_name.str(); 111} 112 113int 114main(int argc, char* const* argv) 115{ 116 int err; 117 bool done = false; 118 while (!done) { 119 int opt = getopt(argc, argv, "f:I:o:hlm:v:d"); 120 switch (opt) 121 { 122 case -1: 123 done = true; 124 break; 125 case 'f': 126 g_listFiles.push_back(string(optarg)); 127 break; 128 case 'I': 129 g_inputBases.push_back(string(optarg)); 130 break; 131 case 'o': 132 if (g_outputBase.length() != 0) { 133 fprintf(stderr, "%s: -o may only be supplied once -- " 134 "-o %s\n", argv[0], optarg); 135 return usage(); 136 } 137 g_outputBase = optarg; 138 break; 139 case 'l': 140 g_useHardLinks = true; 141 break; 142 case 'm': 143 if (g_dependency.length() != 0) { 144 fprintf(stderr, "%s: -m may only be supplied once -- " 145 "-m %s\n", argv[0], optarg); 146 return usage(); 147 } 148 g_dependency = optarg; 149 break; 150 case 'v': 151 if (!add_variable(optarg)) { 152 fprintf(stderr, "%s Invalid expression in '-v %s': " 153 "expected format is '-v VAR=VALUE'.\n", 154 argv[0], optarg); 155 return usage(); 156 } 157 break; 158 case 'd': 159 g_debug = true; 160 break; 161 default: 162 case '?': 163 case 'h': 164 return usage(); 165 } 166 } 167 if (optind != argc) { 168 fprintf(stderr, "%s: invalid argument -- %s\n", argv[0], argv[optind]); 169 return usage(); 170 } 171 172 if (g_listFiles.size() == 0) { 173 fprintf(stderr, "%s: At least one -f option must be supplied.\n", 174 argv[0]); 175 return usage(); 176 } 177 178 if (g_inputBases.size() == 0) { 179 fprintf(stderr, "%s: At least one -I option must be supplied.\n", 180 argv[0]); 181 return usage(); 182 } 183 184 if (g_outputBase.length() == 0) { 185 fprintf(stderr, "%s: -o option must be supplied.\n", argv[0]); 186 return usage(); 187 } 188 189 190#if 0 191 for (vector<string>::iterator it=g_listFiles.begin(); 192 it!=g_listFiles.end(); it++) { 193 printf("-f \"%s\"\n", it->c_str()); 194 } 195 for (vector<string>::iterator it=g_inputBases.begin(); 196 it!=g_inputBases.end(); it++) { 197 printf("-I \"%s\"\n", it->c_str()); 198 } 199 printf("-o \"%s\"\n", g_outputBase.c_str()); 200 if (g_useHardLinks) { 201 printf("-l\n"); 202 } 203#endif 204 205 vector<FileRecord> files; 206 vector<FileRecord> more; 207 vector<string> excludes; 208 set<string> directories; 209 set<string> deleted; 210 211 // read file lists 212 for (vector<string>::iterator it=g_listFiles.begin(); 213 it!=g_listFiles.end(); it++) { 214 err = read_list_file(*it, g_variables, &files, &excludes); 215 if (err != 0) { 216 return err; 217 } 218 } 219 220 // look for input files 221 err = 0; 222 for (vector<FileRecord>::iterator it=files.begin(); 223 it!=files.end(); it++) { 224 err |= locate(&(*it), g_inputBases); 225 } 226 227 // expand the directories that we should copy into a list of files 228 for (vector<FileRecord>::iterator it=files.begin(); 229 it!=files.end(); it++) { 230 if (it->sourceIsDir) { 231 err |= list_dir(*it, excludes, &more); 232 } 233 } 234 for (vector<FileRecord>::iterator it=more.begin(); 235 it!=more.end(); it++) { 236 files.push_back(*it); 237 } 238 239 // get the name and modtime of the output files 240 for (vector<FileRecord>::iterator it=files.begin(); 241 it!=files.end(); it++) { 242 stat_out(g_outputBase, &(*it)); 243 } 244 245 if (err != 0) { 246 return 1; 247 } 248 249 // gather directories 250 for (vector<FileRecord>::iterator it=files.begin(); 251 it!=files.end(); it++) { 252 if (it->sourceIsDir) { 253 directories.insert(it->outPath); 254 } else { 255 string s = dir_part(it->outPath); 256 if (s != ".") { 257 directories.insert(s); 258 } 259 } 260 } 261 262 // gather files that should become directores 263 // and directories that should become files 264 for (vector<FileRecord>::iterator it=files.begin(); 265 it!=files.end(); it++) { 266 if (it->outMod != 0 && it->sourceIsDir != it->outIsDir) { 267 deleted.insert(it->outPath); 268 } 269 } 270 271 // delete files 272 for (set<string>::iterator it=deleted.begin(); 273 it!=deleted.end(); it++) { 274 debug_printf("deleting %s\n", it->c_str()); 275 err = remove_recursively(*it); 276 if (err != 0) { 277 return err; 278 } 279 } 280 281 // remove all files or directories as requested from the input atree file. 282 // must be done before create new directories. 283 for (vector<FileRecord>::iterator it=files.begin(); 284 it!=files.end(); it++) { 285 if (!it->sourceIsDir) { 286 if (it->fileOp == FILE_OP_REMOVE && 287 deleted.count(it->outPath) == 0) { 288 debug_printf("remove %s\n", it->outPath.c_str()); 289 err = remove_recursively(it->outPath); 290 if (err != 0) { 291 return err; 292 } 293 } 294 } 295 } 296 297 // make directories 298 for (set<string>::iterator it=directories.begin(); 299 it!=directories.end(); it++) { 300 debug_printf("mkdir %s\n", it->c_str()); 301 err = mkdir_recursively(*it); 302 if (err != 0) { 303 return err; 304 } 305 } 306 307 // copy (or link) files that are newer or of different size 308 for (vector<FileRecord>::iterator it=files.begin(); 309 it!=files.end(); it++) { 310 if (!it->sourceIsDir) { 311 if (it->fileOp == FILE_OP_REMOVE) { 312 continue; 313 } 314 315 debug_printf("copy %s(%ld) ==> %s(%ld)", 316 it->sourcePath.c_str(), it->sourceMod, 317 it->outPath.c_str(), it->outMod); 318 319 if (it->outSize != it->sourceSize || it->outMod < it->sourceMod) { 320 err = copy_file(it->sourcePath, it->outPath); 321 debug_printf(" done.\n"); 322 if (err != 0) { 323 return err; 324 } 325 } else { 326 debug_printf(" skipping.\n"); 327 } 328 329 if (it->fileOp == FILE_OP_STRIP) { 330 debug_printf("strip %s\n", it->outPath.c_str()); 331 err = strip_file(it->outPath); 332 if (err != 0) { 333 return err; 334 } 335 } 336 } 337 } 338 339 // output the dependency file 340 if (g_dependency.length() != 0) { 341 FILE *f = fopen(g_dependency.c_str(), "w"); 342 if (f != NULL) { 343 fprintf(f, "ATREE_FILES := $(ATREE_FILES) \\\n"); 344 for (vector<FileRecord>::iterator it=files.begin(); 345 it!=files.end(); it++) { 346 if (!it->sourceIsDir) { 347 fprintf(f, "%s \\\n", 348 escape_filename(it->sourcePath).c_str()); 349 } 350 } 351 fprintf(f, "\n"); 352 fclose(f); 353 } else { 354 fprintf(stderr, "error opening manifest file for write: %s\n", 355 g_dependency.c_str()); 356 } 357 } 358 359 return 0; 360} 361