| #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, IN_QUOTE } state = WHITE; |
| while (*p) { |
| if (*p == '#') { |
| break; |
| } |
| |
| switch (state) |
| { |
| case WHITE: |
| if (!isspace(*p)) { |
| q = p; |
| state = (*p == '"') ? IN_QUOTE : TEXT; |
| } |
| break; |
| case IN_QUOTE: |
| if (*p == '"') { |
| state = TEXT; |
| break; |
| } |
| // otherwise fall-through to TEXT case |
| case TEXT: |
| if (state != IN_QUOTE && isspace(*p)) { |
| if (q != p) { |
| const char* start = q; |
| size_t len = p-q; |
| if (len > 2 && *start == '"' && start[len - 1] == '"') { |
| start++; |
| len -= 2; |
| } |
| out->push_back(string(start, len)); |
| } |
| state = WHITE; |
| } |
| break; |
| } |
| p++; |
| } |
| if (state == TEXT) { |
| const char* start = q; |
| size_t len = p-q; |
| if (len > 2 && *start == '"' && start[len - 1] == '"') { |
| start++; |
| len -= 2; |
| } |
| out->push_back(string(start, len)); |
| } |
| } |
| |
| static void |
| add_file(vector<FileRecord>* files, const FileOpType fileOp, |
| const string& listFile, int listLine, |
| const string& sourceName, const string& outName) |
| { |
| FileRecord rec; |
| rec.listFile = listFile; |
| rec.listLine = listLine; |
| rec.fileOp = fileOp; |
| 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 |
| FileOpType op = FILE_OP_COPY; |
| string paths[2]; |
| int pcount = 0; |
| string errstr; |
| for (vector<string>::iterator it = words.begin(); it != words.end(); ++it) { |
| const string& word = *it; |
| if (word == "rm") { |
| if (op != FILE_OP_COPY) { |
| errstr = "Error: you can only specifiy 'rm' or 'strip' once per line."; |
| break; |
| } |
| op = FILE_OP_REMOVE; |
| } else if (word == "strip") { |
| if (op != FILE_OP_COPY) { |
| errstr = "Error: you can only specifiy 'rm' or 'strip' once per line."; |
| break; |
| } |
| op = FILE_OP_STRIP; |
| } else if (pcount < 2) { |
| bool error = false; |
| paths[pcount++] = replace_variables(word, variables, &error); |
| if (error) { |
| err = 1; |
| goto cleanup; |
| } |
| } else { |
| errstr = "Error: More than 2 paths per line."; |
| break; |
| } |
| } |
| |
| if (pcount == 0 && !errstr.empty()) { |
| errstr = "Error: No path found on line."; |
| } |
| |
| if (!errstr.empty()) { |
| fprintf(stderr, "%s:%d: bad format: %s\n%s\nExpected: [SRC] [rm|strip] DEST\n", |
| filename.c_str(), i+1, p, errstr.c_str()); |
| err = 1; |
| } else { |
| if (pcount == 1) { |
| // pattern: [rm|strip] DEST |
| paths[1] = paths[0]; |
| } |
| |
| add_file(files, op, filename, i+1, paths[0], paths[1]); |
| } |
| } |
| p = q; |
| } |
| |
| cleanup: |
| if (buf != NULL) { |
| free(buf); |
| } |
| if (f != NULL) { |
| fclose(f); |
| } |
| return err; |
| } |
| |
| |
| int |
| locate(FileRecord* rec, const vector<string>& search) |
| { |
| if (rec->fileOp == FILE_OP_REMOVE) { |
| // Don't touch source files when removing a destination. |
| rec->sourceMod = 0; |
| rec->sourceSize = 0; |
| rec->sourceIsDir = false; |
| return 0; |
| } |
| |
| 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->sourceSize = st.st_size; |
| 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->outSize = st.st_size; |
| rec->outIsDir = S_ISDIR(st.st_mode); |
| } else { |
| rec->outMod = 0; |
| rec->outSize = 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); |
| } |
| |
| FileRecord::FileRecord() { |
| fileOp = FILE_OP_COPY; |
| } |
| |