files.cpp revision e6b4e5b20ac8a3b01f1a99dd877a98c92036ce5e
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 } 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 = TEXT; 77 } 78 break; 79 case TEXT: 80 if (isspace(*p)) { 81 if (q != p) { 82 out->push_back(string(q, p-q)); 83 } 84 state = WHITE; 85 } 86 break; 87 } 88 p++; 89 } 90 if (state == TEXT) { 91 out->push_back(string(q, p-q)); 92 } 93} 94 95static void 96add_file(vector<FileRecord>* files, const string& listFile, int listLine, 97 const string& sourceName, const string& outName) 98{ 99 FileRecord rec; 100 rec.listFile = listFile; 101 rec.listLine = listLine; 102 rec.sourceName = sourceName; 103 rec.outName = outName; 104 files->push_back(rec); 105} 106 107static string 108replace_variables(const string& input, 109 const map<string, string>& variables, 110 bool* error) { 111 if (variables.empty()) { 112 return input; 113 } 114 115 // Abort if the variable prefix is not found 116 if (input.find("${") == string::npos) { 117 return input; 118 } 119 120 string result = input; 121 122 // Note: rather than be fancy to detect recursive replacements, 123 // we simply iterate till a given threshold is met. 124 125 int retries = 1000; 126 bool did_replace; 127 128 do { 129 did_replace = false; 130 for (map<string, string>::const_iterator it = variables.begin(); 131 it != variables.end(); ++it) { 132 string::size_type pos = 0; 133 while((pos = result.find(it->first, pos)) != string::npos) { 134 result = result.replace(pos, it->first.length(), it->second); 135 pos += it->second.length(); 136 did_replace = true; 137 } 138 } 139 if (did_replace && --retries == 0) { 140 *error = true; 141 fprintf(stderr, "Recursive replacement detected during variables " 142 "substitution. Full list of variables is: "); 143 144 for (map<string, string>::const_iterator it = variables.begin(); 145 it != variables.end(); ++it) { 146 fprintf(stderr, " %s=%s\n", 147 it->first.c_str(), it->second.c_str()); 148 } 149 150 return result; 151 } 152 } while (did_replace); 153 154 return result; 155} 156 157int 158read_list_file(const string& filename, 159 const map<string, string>& variables, 160 vector<FileRecord>* files, 161 vector<string>* excludes) 162{ 163 int err = 0; 164 FILE* f = NULL; 165 long size; 166 char* buf = NULL; 167 char *p, *q; 168 int i, lineCount; 169 170 f = fopen(filename.c_str(), "r"); 171 if (f == NULL) { 172 fprintf(stderr, "Could not open list file (%s): %s\n", 173 filename.c_str(), strerror(errno)); 174 err = errno; 175 goto cleanup; 176 } 177 178 err = fseek(f, 0, SEEK_END); 179 if (err != 0) { 180 fprintf(stderr, "Could not seek to the end of file %s. (%s)\n", 181 filename.c_str(), strerror(errno)); 182 err = errno; 183 goto cleanup; 184 } 185 186 size = ftell(f); 187 188 err = fseek(f, 0, SEEK_SET); 189 if (err != 0) { 190 fprintf(stderr, "Could not seek to the beginning of file %s. (%s)\n", 191 filename.c_str(), strerror(errno)); 192 err = errno; 193 goto cleanup; 194 } 195 196 buf = (char*)malloc(size+1); 197 if (buf == NULL) { 198 // (potentially large) 199 fprintf(stderr, "out of memory (%ld)\n", size); 200 err = ENOMEM; 201 goto cleanup; 202 } 203 204 if (1 != fread(buf, size, 1, f)) { 205 fprintf(stderr, "error reading file %s. (%s)\n", 206 filename.c_str(), strerror(errno)); 207 err = errno; 208 goto cleanup; 209 } 210 211 // split on lines 212 p = buf; 213 q = buf+size; 214 lineCount = 0; 215 while (p<q) { 216 if (*p == '\r' || *p == '\n') { 217 *p = '\0'; 218 lineCount++; 219 } 220 p++; 221 } 222 223 // read lines 224 p = buf; 225 for (i=0; i<lineCount; i++) { 226 int len = strlen(p); 227 q = p + len + 1; 228 if (is_whitespace_line(p) || is_comment_line(p)) { 229 ; 230 } 231 else if (is_exclude_line(p)) { 232 while (*p != '-') p++; 233 p++; 234 excludes->push_back(string(p)); 235 } 236 else { 237 vector<string> words; 238 239 split_line(p, &words); 240 241#if 0 242 printf("[ "); 243 for (size_t k=0; k<words.size(); k++) { 244 printf("'%s' ", words[k].c_str()); 245 } 246 printf("]\n"); 247#endif 248 249 if (words.size() == 1) { 250 // pattern: DEST 251 bool error = false; 252 string w0 = replace_variables(words[0], variables, &error); 253 if (error) { 254 err = 1; 255 goto cleanup; 256 } 257 add_file(files, filename, i+1, w0, w0); 258 } 259 else if (words.size() == 2) { 260 // pattern: SRC DEST 261 bool error = false; 262 string w0, w1; 263 w0 = replace_variables(words[0], variables, &error); 264 if (!error) { 265 w1 = replace_variables(words[1], variables, &error); 266 } 267 if (error) { 268 err = 1; 269 goto cleanup; 270 } 271 add_file(files, filename, i+1, w0, w1); 272 } 273 else { 274 fprintf(stderr, "%s:%d: bad format: %s\n", filename.c_str(), 275 i+1, p); 276 err = 1; 277 } 278 } 279 p = q; 280 } 281 282cleanup: 283 if (buf != NULL) { 284 free(buf); 285 } 286 if (f != NULL) { 287 fclose(f); 288 } 289 return err; 290} 291 292 293int 294locate(FileRecord* rec, const vector<string>& search) 295{ 296 int err; 297 298 for (vector<string>::const_iterator it=search.begin(); 299 it!=search.end(); it++) { 300 string full = path_append(*it, rec->sourceName); 301 struct stat st; 302 err = stat(full.c_str(), &st); 303 if (err == 0) { 304 rec->sourceBase = *it; 305 rec->sourcePath = full; 306 rec->sourceMod = st.st_mtime; 307 rec->sourceIsDir = S_ISDIR(st.st_mode); 308 return 0; 309 } 310 } 311 312 fprintf(stderr, "%s:%d: couldn't locate source file: %s\n", 313 rec->listFile.c_str(), rec->listLine, rec->sourceName.c_str()); 314 return 1; 315} 316 317void 318stat_out(const string& base, FileRecord* rec) 319{ 320 rec->outPath = path_append(base, rec->outName); 321 322 int err; 323 struct stat st; 324 err = stat(rec->outPath.c_str(), &st); 325 if (err == 0) { 326 rec->outMod = st.st_mtime; 327 rec->outIsDir = S_ISDIR(st.st_mode); 328 } else { 329 rec->outMod = 0; 330 rec->outIsDir = false; 331 } 332} 333 334string 335dir_part(const string& filename) 336{ 337 int pos = filename.rfind('/'); 338 if (pos <= 0) { 339 return "."; 340 } 341 return filename.substr(0, pos); 342} 343 344static void 345add_more(const string& entry, bool isDir, 346 const FileRecord& rec, vector<FileRecord>*more) 347{ 348 FileRecord r; 349 r.listFile = rec.listFile; 350 r.listLine = rec.listLine; 351 r.sourceName = path_append(rec.sourceName, entry); 352 r.sourcePath = path_append(rec.sourceBase, r.sourceName); 353 struct stat st; 354 int err = stat(r.sourcePath.c_str(), &st); 355 if (err == 0) { 356 r.sourceMod = st.st_mtime; 357 } 358 r.sourceIsDir = isDir; 359 r.outName = path_append(rec.outName, entry); 360 more->push_back(r); 361} 362 363static bool 364matches_excludes(const char* file, const vector<string>& excludes) 365{ 366 for (vector<string>::const_iterator it=excludes.begin(); 367 it!=excludes.end(); it++) { 368 if (0 == fnmatch(it->c_str(), file, FNM_PERIOD)) { 369 return true; 370 } 371 } 372 return false; 373} 374 375static int 376list_dir(const string& path, const FileRecord& rec, 377 const vector<string>& excludes, 378 vector<FileRecord>* more) 379{ 380 int err; 381 382 string full = path_append(rec.sourceBase, rec.sourceName); 383 full = path_append(full, path); 384 385 DIR *d = opendir(full.c_str()); 386 if (d == NULL) { 387 return errno; 388 } 389 390 vector<string> dirs; 391 392 struct dirent *ent; 393 while (NULL != (ent = readdir(d))) { 394 if (0 == strcmp(".", ent->d_name) 395 || 0 == strcmp("..", ent->d_name)) { 396 continue; 397 } 398 if (matches_excludes(ent->d_name, excludes)) { 399 continue; 400 } 401 string entry = path_append(path, ent->d_name); 402#ifdef HAVE_DIRENT_D_TYPE 403 bool is_directory = (ent->d_type == DT_DIR); 404#else 405 // If dirent.d_type is missing, then use stat instead 406 struct stat stat_buf; 407 stat(entry.c_str(), &stat_buf); 408 bool is_directory = S_ISDIR(stat_buf.st_mode); 409#endif 410 add_more(entry, is_directory, rec, more); 411 if (is_directory) { 412 dirs.push_back(entry); 413 } 414 } 415 closedir(d); 416 417 for (vector<string>::iterator it=dirs.begin(); it!=dirs.end(); it++) { 418 list_dir(*it, rec, excludes, more); 419 } 420 421 return 0; 422} 423 424int 425list_dir(const FileRecord& rec, const vector<string>& excludes, 426 vector<FileRecord>* files) 427{ 428 return list_dir("", rec, excludes, files); 429} 430