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 
usage()59 int usage()
60 {
61     fwrite(USAGE, strlen(USAGE), 1, stderr);
62     return 1;
63 }
64 
65 static bool
add_variable(const char * arg)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
debug_printf(const char * format,...)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
escape_filename(const string name)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
main(int argc,char * const * argv)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