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 int
main(int argc,char * const * argv)94 main(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