Home | History | Annotate | Download | only in atree
      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 
     13 using namespace std;
     14 
     15 bool g_debug = getenv("ATREE_DEBUG") != NULL;
     16 vector<string> g_listFiles;
     17 vector<string> g_inputBases;
     18 map<string, string> g_variables;
     19 string g_outputBase;
     20 string g_dependency;
     21 bool g_useHardLinks = false;
     22 
     23 const 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 
     59 int usage()
     60 {
     61     fwrite(USAGE, strlen(USAGE), 1, stderr);
     62     return 1;
     63 }
     64 
     65 static bool
     66 add_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 
     80 static void
     81 debug_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.
     94 static string
     95 escape_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 
    113 int
    114 main(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