1 #include "files.h"
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <errno.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8 #include <dirent.h>
9 #include <fnmatch.h>
10 #include <string.h>
11 #include <stdlib.h>
12 
13 static bool
is_comment_line(const char * p)14 is_comment_line(const char* p)
15 {
16     while (*p && isspace(*p)) {
17         p++;
18     }
19     return *p == '#';
20 }
21 
22 static string
path_append(const string & base,const string & leaf)23 path_append(const string& base, const string& leaf)
24 {
25     string full = base;
26     if (base.length() > 0 && leaf.length() > 0) {
27         full += '/';
28     }
29     full += leaf;
30     return full;
31 }
32 
33 static bool
is_whitespace_line(const char * p)34 is_whitespace_line(const char* p)
35 {
36     while (*p) {
37         if (!isspace(*p)) {
38             return false;
39         }
40         p++;
41     }
42     return true;
43 }
44 
45 static bool
is_exclude_line(const char * p)46 is_exclude_line(const char* p) {
47     while (*p) {
48         if (*p == '-') {
49             return true;
50         }
51         else if (isspace(*p)) {
52             p++;
53         }
54         else {
55             return false;
56         }
57     }
58     return false;
59 }
60 
61 void
split_line(const char * p,vector<string> * out)62 split_line(const char* p, vector<string>* out)
63 {
64     const char* q = p;
65     enum { WHITE, TEXT, IN_QUOTE } state = WHITE;
66     while (*p) {
67         if (*p == '#') {
68             break;
69         }
70 
71         switch (state)
72         {
73             case WHITE:
74                 if (!isspace(*p)) {
75                     q = p;
76                     state = (*p == '"') ? IN_QUOTE : TEXT;
77                 }
78                 break;
79             case IN_QUOTE:
80                 if (*p == '"') {
81                     state = TEXT;
82                     break;
83                 }
84                 [[fallthrough]];
85             case TEXT:
86                 if (state != IN_QUOTE && isspace(*p)) {
87                     if (q != p) {
88                         const char* start = q;
89                         size_t len = p-q;
90                         if (len > 2 && *start == '"' && start[len - 1] == '"') {
91                             start++;
92                             len -= 2;
93                         }
94                         out->push_back(string(start, len));
95                     }
96                     state = WHITE;
97                 }
98                 break;
99         }
100         p++;
101     }
102     if (state == TEXT) {
103         const char* start = q;
104         size_t len = p-q;
105         if (len > 2 && *start == '"' && start[len - 1] == '"') {
106             start++;
107             len -= 2;
108         }
109         out->push_back(string(start, len));
110     }
111 }
112 
113 static void
add_file(vector<FileRecord> * files,const FileOpType fileOp,const string & listFile,int listLine,const string & sourceName,const string & outName)114 add_file(vector<FileRecord>* files, const FileOpType fileOp,
115             const string& listFile, int listLine,
116             const string& sourceName, const string& outName)
117 {
118     FileRecord rec;
119     rec.listFile = listFile;
120     rec.listLine = listLine;
121     rec.fileOp = fileOp;
122     rec.sourceName = sourceName;
123     rec.outName = outName;
124     files->push_back(rec);
125 }
126 
127 static string
replace_variables(const string & input,const map<string,string> & variables,bool * error)128 replace_variables(const string& input,
129                   const map<string, string>& variables,
130                   bool* error) {
131     if (variables.empty()) {
132         return input;
133     }
134 
135     // Abort if the variable prefix is not found
136     if (input.find("${") == string::npos) {
137         return input;
138     }
139 
140     string result = input;
141 
142     // Note: rather than be fancy to detect recursive replacements,
143     // we simply iterate till a given threshold is met.
144 
145     int retries = 1000;
146     bool did_replace;
147 
148     do {
149         did_replace = false;
150         for (map<string, string>::const_iterator it = variables.begin();
151              it != variables.end(); ++it) {
152             string::size_type pos = 0;
153             while((pos = result.find(it->first, pos)) != string::npos) {
154                 result = result.replace(pos, it->first.length(), it->second);
155                 pos += it->second.length();
156                 did_replace = true;
157             }
158         }
159         if (did_replace && --retries == 0) {
160             *error = true;
161             fprintf(stderr, "Recursive replacement detected during variables "
162                     "substitution. Full list of variables is: ");
163 
164             for (map<string, string>::const_iterator it = variables.begin();
165                  it != variables.end(); ++it) {
166                 fprintf(stderr, "  %s=%s\n",
167                         it->first.c_str(), it->second.c_str());
168             }
169 
170             return result;
171         }
172     } while (did_replace);
173 
174     return result;
175 }
176 
177 int
read_list_file(const string & filename,const map<string,string> & variables,vector<FileRecord> * files,vector<string> * excludes)178 read_list_file(const string& filename,
179                const map<string, string>& variables,
180                vector<FileRecord>* files,
181                vector<string>* excludes)
182 {
183     int err = 0;
184     FILE* f = NULL;
185     long size;
186     char* buf = NULL;
187     char *p, *q;
188     int i, lineCount;
189 
190     f = fopen(filename.c_str(), "r");
191     if (f == NULL) {
192         fprintf(stderr, "Could not open list file (%s): %s\n",
193                     filename.c_str(), strerror(errno));
194         err = errno;
195         goto cleanup;
196     }
197 
198     err = fseek(f, 0, SEEK_END);
199     if (err != 0) {
200         fprintf(stderr, "Could not seek to the end of file %s. (%s)\n",
201                     filename.c_str(), strerror(errno));
202         err = errno;
203         goto cleanup;
204     }
205 
206     size = ftell(f);
207 
208     err = fseek(f, 0, SEEK_SET);
209     if (err != 0) {
210         fprintf(stderr, "Could not seek to the beginning of file %s. (%s)\n",
211                     filename.c_str(), strerror(errno));
212         err = errno;
213         goto cleanup;
214     }
215 
216     buf = (char*)malloc(size+1);
217     if (buf == NULL) {
218         // (potentially large)
219         fprintf(stderr, "out of memory (%ld)\n", size);
220         err = ENOMEM;
221         goto cleanup;
222     }
223 
224     if (1 != fread(buf, size, 1, f)) {
225         fprintf(stderr, "error reading file %s. (%s)\n",
226                     filename.c_str(), strerror(errno));
227         err = errno;
228         goto cleanup;
229     }
230 
231     // split on lines
232     p = buf;
233     q = buf+size;
234     lineCount = 0;
235     while (p<q) {
236         if (*p == '\r' || *p == '\n') {
237             *p = '\0';
238             lineCount++;
239         }
240         p++;
241     }
242 
243     // read lines
244     p = buf;
245     for (i=0; i<lineCount; i++) {
246         int len = strlen(p);
247         q = p + len + 1;
248         if (is_whitespace_line(p) || is_comment_line(p)) {
249             ;
250         }
251         else if (is_exclude_line(p)) {
252             while (*p != '-') p++;
253             p++;
254             excludes->push_back(string(p));
255         }
256         else {
257             vector<string> words;
258 
259             split_line(p, &words);
260 
261 #if 0
262             printf("[ ");
263             for (size_t k=0; k<words.size(); k++) {
264                 printf("'%s' ", words[k].c_str());
265             }
266             printf("]\n");
267 #endif
268             FileOpType op = FILE_OP_COPY;
269             string paths[2];
270             int pcount = 0;
271             string errstr;
272             for (vector<string>::iterator it = words.begin(); it != words.end(); ++it) {
273                 const string& word = *it;
274                 if (word == "rm") {
275                     if (op != FILE_OP_COPY) {
276                         errstr = "Error: you can only specifiy 'rm' or 'strip' once per line.";
277                         break;
278                     }
279                     op = FILE_OP_REMOVE;
280                 } else if (word == "strip") {
281                     if (op != FILE_OP_COPY) {
282                         errstr = "Error: you can only specifiy 'rm' or 'strip' once per line.";
283                         break;
284                     }
285                     op = FILE_OP_STRIP;
286                 } else if (pcount < 2) {
287                     bool error = false;
288                     paths[pcount++] = replace_variables(word, variables, &error);
289                     if (error) {
290                         err = 1;
291                         goto cleanup;
292                     }
293                 } else {
294                     errstr = "Error: More than 2 paths per line.";
295                     break;
296                 }
297             }
298 
299             if (pcount == 0 && !errstr.empty()) {
300                 errstr = "Error: No path found on line.";
301             }
302 
303             if (!errstr.empty()) {
304                 fprintf(stderr, "%s:%d: bad format: %s\n%s\nExpected: [SRC] [rm|strip] DEST\n",
305                         filename.c_str(), i+1, p, errstr.c_str());
306                 err = 1;
307             } else {
308                 if (pcount == 1) {
309                     // pattern: [rm|strip] DEST
310                     paths[1] = paths[0];
311                 }
312 
313                 add_file(files, op, filename, i+1, paths[0], paths[1]);
314             }
315         }
316         p = q;
317     }
318 
319 cleanup:
320     if (buf != NULL) {
321         free(buf);
322     }
323     if (f != NULL) {
324         fclose(f);
325     }
326     return err;
327 }
328 
329 
330 int
locate(FileRecord * rec,const vector<string> & search)331 locate(FileRecord* rec, const vector<string>& search)
332 {
333     if (rec->fileOp == FILE_OP_REMOVE) {
334         // Don't touch source files when removing a destination.
335         rec->sourceMod = 0;
336         rec->sourceSize = 0;
337         rec->sourceIsDir = false;
338         return 0;
339     }
340 
341     int err;
342 
343     for (vector<string>::const_iterator it=search.begin();
344                 it!=search.end(); it++) {
345         string full = path_append(*it, rec->sourceName);
346         struct stat st;
347         err = stat(full.c_str(), &st);
348         if (err == 0) {
349             rec->sourceBase = *it;
350             rec->sourcePath = full;
351             rec->sourceMod = st.st_mtime;
352             rec->sourceSize = st.st_size;
353             rec->sourceIsDir = S_ISDIR(st.st_mode);
354             return 0;
355         }
356     }
357 
358     fprintf(stderr, "%s:%d: couldn't locate source file: %s\n",
359                 rec->listFile.c_str(), rec->listLine, rec->sourceName.c_str());
360     return 1;
361 }
362 
363 void
stat_out(const string & base,FileRecord * rec)364 stat_out(const string& base, FileRecord* rec)
365 {
366     rec->outPath = path_append(base, rec->outName);
367 
368     int err;
369     struct stat st;
370     err = stat(rec->outPath.c_str(), &st);
371     if (err == 0) {
372         rec->outMod = st.st_mtime;
373         rec->outSize = st.st_size;
374         rec->outIsDir = S_ISDIR(st.st_mode);
375     } else {
376         rec->outMod = 0;
377         rec->outSize = 0;
378         rec->outIsDir = false;
379     }
380 }
381 
382 string
dir_part(const string & filename)383 dir_part(const string& filename)
384 {
385     int pos = filename.rfind('/');
386     if (pos <= 0) {
387         return ".";
388     }
389     return filename.substr(0, pos);
390 }
391 
392 static void
add_more(const string & entry,bool isDir,const FileRecord & rec,vector<FileRecord> * more)393 add_more(const string& entry, bool isDir,
394          const FileRecord& rec, vector<FileRecord>*more)
395 {
396     FileRecord r;
397     r.listFile = rec.listFile;
398     r.listLine = rec.listLine;
399     r.sourceName = path_append(rec.sourceName, entry);
400     r.sourcePath = path_append(rec.sourceBase, r.sourceName);
401     struct stat st;
402     int err = stat(r.sourcePath.c_str(), &st);
403     if (err == 0) {
404         r.sourceMod = st.st_mtime;
405     }
406     r.sourceIsDir = isDir;
407     r.outName = path_append(rec.outName, entry);
408     more->push_back(r);
409 }
410 
411 static bool
matches_excludes(const char * file,const vector<string> & excludes)412 matches_excludes(const char* file, const vector<string>& excludes)
413 {
414     for (vector<string>::const_iterator it=excludes.begin();
415             it!=excludes.end(); it++) {
416         if (0 == fnmatch(it->c_str(), file, FNM_PERIOD)) {
417             return true;
418         }
419     }
420     return false;
421 }
422 
423 static int
list_dir(const string & path,const FileRecord & rec,const vector<string> & excludes,vector<FileRecord> * more)424 list_dir(const string& path, const FileRecord& rec,
425                 const vector<string>& excludes,
426                 vector<FileRecord>* more)
427 {
428     string full = path_append(rec.sourceBase, rec.sourceName);
429     full = path_append(full, path);
430 
431     DIR *d = opendir(full.c_str());
432     if (d == NULL) {
433         return errno;
434     }
435 
436     vector<string> dirs;
437 
438     struct dirent *ent;
439     while (NULL != (ent = readdir(d))) {
440         if (0 == strcmp(".", ent->d_name)
441                 || 0 == strcmp("..", ent->d_name)) {
442             continue;
443         }
444         if (matches_excludes(ent->d_name, excludes)) {
445             continue;
446         }
447         string entry = path_append(path, ent->d_name);
448         bool is_directory = (ent->d_type == DT_DIR);
449         add_more(entry, is_directory, rec, more);
450         if (is_directory) {
451             dirs.push_back(entry);
452         }
453     }
454     closedir(d);
455 
456     for (vector<string>::iterator it=dirs.begin(); it!=dirs.end(); it++) {
457         list_dir(*it, rec, excludes, more);
458     }
459 
460     return 0;
461 }
462 
463 int
list_dir(const FileRecord & rec,const vector<string> & excludes,vector<FileRecord> * files)464 list_dir(const FileRecord& rec, const vector<string>& excludes,
465             vector<FileRecord>* files)
466 {
467     return list_dir("", rec, excludes, files);
468 }
469 
FileRecord()470 FileRecord::FileRecord() {
471     fileOp = FILE_OP_COPY;
472 }
473 
474