atree.cpp revision 131292c0422b4e6eca5ee0eda431c9faae5cf35d
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
93int
94main(int argc, char* const* argv)
95{
96    int err;
97    bool done = false;
98    while (!done) {
99        int opt = getopt(argc, argv, "f:I:o:hlm:v:d");
100        switch (opt)
101        {
102            case -1:
103                done = true;
104                break;
105            case 'f':
106                g_listFiles.push_back(string(optarg));
107                break;
108            case 'I':
109                g_inputBases.push_back(string(optarg));
110                break;
111            case 'o':
112                if (g_outputBase.length() != 0) {
113                    fprintf(stderr, "%s: -o may only be supplied once -- "
114                                "-o %s\n", argv[0], optarg);
115                    return usage();
116                }
117                g_outputBase = optarg;
118                break;
119            case 'l':
120                g_useHardLinks = true;
121                break;
122            case 'm':
123                if (g_dependency.length() != 0) {
124                    fprintf(stderr, "%s: -m may only be supplied once -- "
125                                "-m %s\n", argv[0], optarg);
126                    return usage();
127                }
128                g_dependency = optarg;
129                break;
130            case 'v':
131                if (!add_variable(optarg)) {
132                    fprintf(stderr, "%s Invalid expression in '-v %s': "
133                            "expected format is '-v VAR=VALUE'.\n",
134                            argv[0], optarg);
135                    return usage();
136                }
137                break;
138            case 'd':
139                g_debug = true;
140                break;
141            default:
142            case '?':
143            case 'h':
144                return usage();
145        }
146    }
147    if (optind != argc) {
148        fprintf(stderr, "%s: invalid argument -- %s\n", argv[0], argv[optind]);
149        return usage();
150    }
151
152    if (g_listFiles.size() == 0) {
153        fprintf(stderr, "%s: At least one -f option must be supplied.\n",
154                 argv[0]);
155        return usage();
156    }
157
158    if (g_inputBases.size() == 0) {
159        fprintf(stderr, "%s: At least one -I option must be supplied.\n",
160                 argv[0]);
161        return usage();
162    }
163
164    if (g_outputBase.length() == 0) {
165        fprintf(stderr, "%s: -o option must be supplied.\n", argv[0]);
166        return usage();
167    }
168
169
170#if 0
171    for (vector<string>::iterator it=g_listFiles.begin();
172                                it!=g_listFiles.end(); it++) {
173        printf("-f \"%s\"\n", it->c_str());
174    }
175    for (vector<string>::iterator it=g_inputBases.begin();
176                                it!=g_inputBases.end(); it++) {
177        printf("-I \"%s\"\n", it->c_str());
178    }
179    printf("-o \"%s\"\n", g_outputBase.c_str());
180    if (g_useHardLinks) {
181        printf("-l\n");
182    }
183#endif
184
185    vector<FileRecord> files;
186    vector<FileRecord> more;
187    vector<string> excludes;
188    set<string> directories;
189    set<string> deleted;
190
191    // read file lists
192    for (vector<string>::iterator it=g_listFiles.begin();
193                                 it!=g_listFiles.end(); it++) {
194        err = read_list_file(*it, g_variables, &files, &excludes);
195        if (err != 0) {
196            return err;
197        }
198    }
199
200    // look for input files
201    err = 0;
202    for (vector<FileRecord>::iterator it=files.begin();
203                                it!=files.end(); it++) {
204        err |= locate(&(*it), g_inputBases);
205    }
206
207    // expand the directories that we should copy into a list of files
208    for (vector<FileRecord>::iterator it=files.begin();
209                                it!=files.end(); it++) {
210        if (it->sourceIsDir) {
211            err |= list_dir(*it, excludes, &more);
212        }
213    }
214    for (vector<FileRecord>::iterator it=more.begin();
215                                it!=more.end(); it++) {
216        files.push_back(*it);
217    }
218
219    // get the name and modtime of the output files
220    for (vector<FileRecord>::iterator it=files.begin();
221                                it!=files.end(); it++) {
222        stat_out(g_outputBase, &(*it));
223    }
224
225    if (err != 0) {
226        return 1;
227    }
228
229    // gather directories
230    for (vector<FileRecord>::iterator it=files.begin();
231                                it!=files.end(); it++) {
232        if (it->sourceIsDir) {
233            directories.insert(it->outPath);
234        } else {
235            string s = dir_part(it->outPath);
236            if (s != ".") {
237                directories.insert(s);
238            }
239        }
240    }
241
242    // gather files that should become directores
243    // and directories that should become files
244    for (vector<FileRecord>::iterator it=files.begin();
245                                it!=files.end(); it++) {
246        if (it->outMod != 0 && it->sourceIsDir != it->outIsDir) {
247            deleted.insert(it->outPath);
248        }
249    }
250
251    // delete files
252    for (set<string>::iterator it=deleted.begin();
253                                it!=deleted.end(); it++) {
254        debug_printf("deleting %s\n", it->c_str());
255        err = remove_recursively(*it);
256        if (err != 0) {
257            return err;
258        }
259    }
260
261    // remove all files or directories as requested from the input atree file.
262    // must be done before create new directories.
263    for (vector<FileRecord>::iterator it=files.begin();
264                                it!=files.end(); it++) {
265        if (!it->sourceIsDir) {
266            if (it->fileOp == FILE_OP_REMOVE &&
267                    deleted.count(it->outPath) == 0) {
268                debug_printf("remove %s\n", it->outPath.c_str());
269                err = remove_recursively(it->outPath);
270                if (err != 0) {
271                    return err;
272                }
273            }
274        }
275    }
276
277    // make directories
278    for (set<string>::iterator it=directories.begin();
279                                it!=directories.end(); it++) {
280        debug_printf("mkdir %s\n", it->c_str());
281        err = mkdir_recursively(*it);
282        if (err != 0) {
283            return err;
284        }
285    }
286
287    // copy (or link) files that are newer or of different size
288    for (vector<FileRecord>::iterator it=files.begin();
289                                it!=files.end(); it++) {
290        if (!it->sourceIsDir) {
291            if (it->fileOp == FILE_OP_REMOVE) {
292                continue;
293            }
294
295            debug_printf("copy %s(%ld) ==> %s(%ld)",
296                it->sourcePath.c_str(), it->sourceMod,
297                it->outPath.c_str(), it->outMod);
298
299            if (it->outSize != it->sourceSize || it->outMod < it->sourceMod) {
300                err = copy_file(it->sourcePath, it->outPath);
301                debug_printf(" done.\n");
302                if (err != 0) {
303                    return err;
304                }
305            } else {
306                debug_printf(" skipping.\n");
307            }
308
309            if (it->fileOp == FILE_OP_STRIP) {
310                debug_printf("strip %s\n", it->outPath.c_str());
311                err = strip_file(it->outPath);
312                if (err != 0) {
313                    return err;
314                }
315            }
316        }
317    }
318
319    // output the dependency file
320    if (g_dependency.length() != 0) {
321        FILE *f = fopen(g_dependency.c_str(), "w");
322        if (f != NULL) {
323            fprintf(f, "ATREE_FILES := $(ATREE_FILES) \\\n");
324            for (vector<FileRecord>::iterator it=files.begin();
325                                it!=files.end(); it++) {
326                if (!it->sourceIsDir) {
327                    fprintf(f, "%s \\\n", it->sourcePath.c_str());
328                }
329            }
330            fprintf(f, "\n");
331            fclose(f);
332        } else {
333            fprintf(stderr, "error opening manifest file for write: %s\n",
334                    g_dependency.c_str());
335        }
336    }
337
338    return 0;
339}
340