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 13static bool 14is_comment_line(const char* p) 15{ 16 while (*p && isspace(*p)) { 17 p++; 18 } 19 return *p == '#'; 20} 21 22static string 23path_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 33static bool 34is_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 45static bool 46is_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 61void 62split_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 // otherwise fall-through to TEXT case 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 113static void 114add_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 127static string 128replace_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 177int 178read_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 319cleanup: 320 if (buf != NULL) { 321 free(buf); 322 } 323 if (f != NULL) { 324 fclose(f); 325 } 326 return err; 327} 328 329 330int 331locate(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 363void 364stat_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 382string 383dir_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 392static void 393add_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 411static bool 412matches_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 423static int 424list_dir(const string& path, const FileRecord& rec, 425 const vector<string>& excludes, 426 vector<FileRecord>* more) 427{ 428 int err; 429 430 string full = path_append(rec.sourceBase, rec.sourceName); 431 full = path_append(full, path); 432 433 DIR *d = opendir(full.c_str()); 434 if (d == NULL) { 435 return errno; 436 } 437 438 vector<string> dirs; 439 440 struct dirent *ent; 441 while (NULL != (ent = readdir(d))) { 442 if (0 == strcmp(".", ent->d_name) 443 || 0 == strcmp("..", ent->d_name)) { 444 continue; 445 } 446 if (matches_excludes(ent->d_name, excludes)) { 447 continue; 448 } 449 string entry = path_append(path, ent->d_name); 450#ifdef HAVE_DIRENT_D_TYPE 451 bool is_directory = (ent->d_type == DT_DIR); 452#else 453 // If dirent.d_type is missing, then use stat instead 454 struct stat stat_buf; 455 stat(entry.c_str(), &stat_buf); 456 bool is_directory = S_ISDIR(stat_buf.st_mode); 457#endif 458 add_more(entry, is_directory, rec, more); 459 if (is_directory) { 460 dirs.push_back(entry); 461 } 462 } 463 closedir(d); 464 465 for (vector<string>::iterator it=dirs.begin(); it!=dirs.end(); it++) { 466 list_dir(*it, rec, excludes, more); 467 } 468 469 return 0; 470} 471 472int 473list_dir(const FileRecord& rec, const vector<string>& excludes, 474 vector<FileRecord>* files) 475{ 476 return list_dir("", rec, excludes, files); 477} 478 479FileRecord::FileRecord() { 480 fileOp = FILE_OP_COPY; 481} 482 483