#include "files.h" #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> #include <fnmatch.h> #include <string.h> #include <stdlib.h> static bool is_comment_line(const char* p) { while (*p && isspace(*p)) { p++; } return *p == '#'; } static string path_append(const string& base, const string& leaf) { string full = base; if (base.length() > 0 && leaf.length() > 0) { full += '/'; } full += leaf; return full; } static bool is_whitespace_line(const char* p) { while (*p) { if (!isspace(*p)) { return false; } p++; } return true; } static bool is_exclude_line(const char* p) { while (*p) { if (*p == '-') { return true; } else if (isspace(*p)) { p++; } else { return false; } } return false; } void split_line(const char* p, vector<string>* out) { const char* q = p; enum { WHITE, TEXT } state = WHITE; while (*p) { if (*p == '#') { break; } switch (state) { case WHITE: if (!isspace(*p)) { q = p; state = TEXT; } break; case TEXT: if (isspace(*p)) { if (q != p) { out->push_back(string(q, p-q)); } state = WHITE; } break; } p++; } if (state == TEXT) { out->push_back(string(q, p-q)); } } static void add_file(vector<FileRecord>* files, const string& listFile, int listLine, const string& sourceName, const string& outName) { FileRecord rec; rec.listFile = listFile; rec.listLine = listLine; rec.sourceName = sourceName; rec.outName = outName; files->push_back(rec); } static string replace_variables(const string& input, const map<string, string>& variables, bool* error) { if (variables.empty()) { return input; } // Abort if the variable prefix is not found if (input.find("${") == string::npos) { return input; } string result = input; // Note: rather than be fancy to detect recursive replacements, // we simply iterate till a given threshold is met. int retries = 1000; bool did_replace; do { did_replace = false; for (map<string, string>::const_iterator it = variables.begin(); it != variables.end(); ++it) { string::size_type pos = 0; while((pos = result.find(it->first, pos)) != string::npos) { result = result.replace(pos, it->first.length(), it->second); pos += it->second.length(); did_replace = true; } } if (did_replace && --retries == 0) { *error = true; fprintf(stderr, "Recursive replacement detected during variables " "substitution. Full list of variables is: "); for (map<string, string>::const_iterator it = variables.begin(); it != variables.end(); ++it) { fprintf(stderr, " %s=%s\n", it->first.c_str(), it->second.c_str()); } return result; } } while (did_replace); return result; } int read_list_file(const string& filename, const map<string, string>& variables, vector<FileRecord>* files, vector<string>* excludes) { int err = 0; FILE* f = NULL; long size; char* buf = NULL; char *p, *q; int i, lineCount; f = fopen(filename.c_str(), "r"); if (f == NULL) { fprintf(stderr, "Could not open list file (%s): %s\n", filename.c_str(), strerror(errno)); err = errno; goto cleanup; } err = fseek(f, 0, SEEK_END); if (err != 0) { fprintf(stderr, "Could not seek to the end of file %s. (%s)\n", filename.c_str(), strerror(errno)); err = errno; goto cleanup; } size = ftell(f); err = fseek(f, 0, SEEK_SET); if (err != 0) { fprintf(stderr, "Could not seek to the beginning of file %s. (%s)\n", filename.c_str(), strerror(errno)); err = errno; goto cleanup; } buf = (char*)malloc(size+1); if (buf == NULL) { // (potentially large) fprintf(stderr, "out of memory (%ld)\n", size); err = ENOMEM; goto cleanup; } if (1 != fread(buf, size, 1, f)) { fprintf(stderr, "error reading file %s. (%s)\n", filename.c_str(), strerror(errno)); err = errno; goto cleanup; } // split on lines p = buf; q = buf+size; lineCount = 0; while (p<q) { if (*p == '\r' || *p == '\n') { *p = '\0'; lineCount++; } p++; } // read lines p = buf; for (i=0; i<lineCount; i++) { int len = strlen(p); q = p + len + 1; if (is_whitespace_line(p) || is_comment_line(p)) { ; } else if (is_exclude_line(p)) { while (*p != '-') p++; p++; excludes->push_back(string(p)); } else { vector<string> words; split_line(p, &words); #if 0 printf("[ "); for (size_t k=0; k<words.size(); k++) { printf("'%s' ", words[k].c_str()); } printf("]\n"); #endif if (words.size() == 1) { // pattern: DEST bool error = false; string w0 = replace_variables(words[0], variables, &error); if (error) { err = 1; goto cleanup; } add_file(files, filename, i+1, w0, w0); } else if (words.size() == 2) { // pattern: SRC DEST bool error = false; string w0, w1; w0 = replace_variables(words[0], variables, &error); if (!error) { w1 = replace_variables(words[1], variables, &error); } if (error) { err = 1; goto cleanup; } add_file(files, filename, i+1, w0, w1); } else { fprintf(stderr, "%s:%d: bad format: %s\n", filename.c_str(), i+1, p); err = 1; } } p = q; } cleanup: if (buf != NULL) { free(buf); } if (f != NULL) { fclose(f); } return err; } int locate(FileRecord* rec, const vector<string>& search) { int err; for (vector<string>::const_iterator it=search.begin(); it!=search.end(); it++) { string full = path_append(*it, rec->sourceName); struct stat st; err = stat(full.c_str(), &st); if (err == 0) { rec->sourceBase = *it; rec->sourcePath = full; rec->sourceMod = st.st_mtime; rec->sourceIsDir = S_ISDIR(st.st_mode); return 0; } } fprintf(stderr, "%s:%d: couldn't locate source file: %s\n", rec->listFile.c_str(), rec->listLine, rec->sourceName.c_str()); return 1; } void stat_out(const string& base, FileRecord* rec) { rec->outPath = path_append(base, rec->outName); int err; struct stat st; err = stat(rec->outPath.c_str(), &st); if (err == 0) { rec->outMod = st.st_mtime; rec->outIsDir = S_ISDIR(st.st_mode); } else { rec->outMod = 0; rec->outIsDir = false; } } string dir_part(const string& filename) { int pos = filename.rfind('/'); if (pos <= 0) { return "."; } return filename.substr(0, pos); } static void add_more(const string& entry, bool isDir, const FileRecord& rec, vector<FileRecord>*more) { FileRecord r; r.listFile = rec.listFile; r.listLine = rec.listLine; r.sourceName = path_append(rec.sourceName, entry); r.sourcePath = path_append(rec.sourceBase, r.sourceName); struct stat st; int err = stat(r.sourcePath.c_str(), &st); if (err == 0) { r.sourceMod = st.st_mtime; } r.sourceIsDir = isDir; r.outName = path_append(rec.outName, entry); more->push_back(r); } static bool matches_excludes(const char* file, const vector<string>& excludes) { for (vector<string>::const_iterator it=excludes.begin(); it!=excludes.end(); it++) { if (0 == fnmatch(it->c_str(), file, FNM_PERIOD)) { return true; } } return false; } static int list_dir(const string& path, const FileRecord& rec, const vector<string>& excludes, vector<FileRecord>* more) { int err; string full = path_append(rec.sourceBase, rec.sourceName); full = path_append(full, path); DIR *d = opendir(full.c_str()); if (d == NULL) { return errno; } vector<string> dirs; struct dirent *ent; while (NULL != (ent = readdir(d))) { if (0 == strcmp(".", ent->d_name) || 0 == strcmp("..", ent->d_name)) { continue; } if (matches_excludes(ent->d_name, excludes)) { continue; } string entry = path_append(path, ent->d_name); #ifdef HAVE_DIRENT_D_TYPE bool is_directory = (ent->d_type == DT_DIR); #else // If dirent.d_type is missing, then use stat instead struct stat stat_buf; stat(entry.c_str(), &stat_buf); bool is_directory = S_ISDIR(stat_buf.st_mode); #endif add_more(entry, is_directory, rec, more); if (is_directory) { dirs.push_back(entry); } } closedir(d); for (vector<string>::iterator it=dirs.begin(); it!=dirs.end(); it++) { list_dir(*it, rec, excludes, more); } return 0; } int list_dir(const FileRecord& rec, const vector<string>& excludes, vector<FileRecord>* files) { return list_dir("", rec, excludes, files); }