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