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