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