X-Git-Url: http://git.vrable.net/?a=blobdiff_plain;f=statcache.cc;h=86886c0e4a68cb72483244895e45b142bb69ff5f;hb=bf947741ac8f65e74d594a1e14e94d90320b403e;hp=03cfd950b27967ddc4edf53ac51c03a5594a4f26;hpb=0e55c5307b65af2615f28c61c0026d80c250f0d6;p=cumulus.git diff --git a/statcache.cc b/statcache.cc index 03cfd95..86886c0 100644 --- a/statcache.cc +++ b/statcache.cc @@ -23,27 +23,74 @@ #include #include #include +#include #include #include +#include #include -#include "format.h" +#include "ref.h" #include "statcache.h" +#include "util.h" using std::list; +using std::map; using std::string; +using std::getline; using std::ifstream; using std::ofstream; +/* Like strcmp, but sorts in the order that files will be visited in the + * filesystem. That is, we break paths apart at slashes, and compare path + * components separately. */ +static int pathcmp(const char *path1, const char *path2) +{ + /* Find the first component in each path. */ + const char *slash1 = strchr(path1, '/'); + const char *slash2 = strchr(path2, '/'); + + { + string comp1, comp2; + if (slash1 == NULL) + comp1 = path1; + else + comp1 = string(path1, slash1 - path1); + + if (slash2 == NULL) + comp2 = path2; + else + comp2 = string(path2, slash2 - path2); + + /* Directly compare the two components first. */ + if (comp1 < comp2) + return -1; + if (comp1 > comp2) + return 1; + } + + if (slash1 == NULL && slash2 == NULL) + return 0; + if (slash1 == NULL) + return -1; + if (slash2 == NULL) + return 1; + + return pathcmp(slash1 + 1, slash2 + 1); +} + void StatCache::Open(const char *path, const char *snapshot_name) { oldpath = path; oldpath += "/statcache"; newpath = oldpath + "." + snapshot_name; - oldcache = NULL; + oldcache = new ifstream(oldpath.c_str()); newcache = new ofstream(newpath.c_str()); + + /* Read the first entry from the old stat cache into memory before we + * start. */ + ReadNext(); } void StatCache::Close() @@ -59,14 +106,161 @@ void StatCache::Close() } } +/* Read the next entry from the old statcache file and cache it in memory. */ +void StatCache::ReadNext() +{ + if (oldcache == NULL) { + end_of_cache = true; + return; + } + + std::istream &cache = *oldcache; + map fields; + + old_is_validated = false; + old_mtime = -1; + old_ctime = -1; + old_inode = -1; + old_size = -1; + old_checksum = ""; + old_contents.clear(); + + /* First, read in the filename. */ + getline(cache, old_name); + if (!cache) { + end_of_cache = true; + return; + } + old_name = uri_decode(old_name); + + /* Start reading in the fields which follow the filename. */ + string field = ""; + while (!cache.eof()) { + string line; + getline(cache, line); + const char *s = line.c_str(); + + /* Is the line blank? If so, we have reached the end of this entry. */ + if (s[0] == '\0' || s[0] == '\n') + break; + + /* Is this a continuation line? (Does it start with whitespace?) */ + if (isspace(s[0]) && field != "") { + fields[field] += line; + continue; + } + + /* For lines of the form "Key: Value" look for ':' and split the line + * apart. */ + const char *value = strchr(s, ':'); + if (value == NULL) + continue; + field = string(s, value - s); + + value++; + while (isspace(*value)) + value++; + + fields[field] = value; + } + + /* Parse the easy fields: mtime, ctime, inode, checksum, ... */ + if (fields.count("validated")) + old_is_validated = true; + if (fields.count("mtime")) + old_mtime = parse_int(fields["mtime"]); + if (fields.count("ctime")) + old_ctime = parse_int(fields["ctime"]); + if (fields.count("inode")) + old_inode = parse_int(fields["inode"]); + if (fields.count("size")) + old_size = parse_int(fields["size"]); + + old_checksum = fields["checksum"]; + + /* Parse the list of blocks. */ + const char *s = fields["blocks"].c_str(); + while (*s != '\0') { + if (isspace(*s)) { + s++; + continue; + } + + string ref = ""; + while (*s != '\0' && !isspace(*s)) { + char buf[2]; + buf[0] = *s; + buf[1] = '\0'; + ref += buf; + s++; + } + + ObjectReference *r = ObjectReference::parse(ref); + if (r != NULL) { + old_contents.push_back(*r); + delete r; + } + } + + end_of_cache = false; +} + +/* Find information about the given filename in the old stat cache, if it + * exists. */ +bool StatCache::Find(const string &path, const struct stat *stat_buf) +{ + while (!end_of_cache && pathcmp(old_name.c_str(), path.c_str()) < 0) + ReadNext(); + + /* Could the file be found at all? */ + if (end_of_cache) + return false; + if (old_name != path) + return false; + + /* Do we trust cached stat information? */ + if (!old_is_validated) + return false; + + /* Check to see if the file is unchanged. */ + if (stat_buf->st_mtime != old_mtime) + return false; + if (stat_buf->st_ctime != old_ctime) + return false; + if ((long long)stat_buf->st_ino != old_inode) + return false; + if (stat_buf->st_size != old_size) + return false; + + /* File looks to be unchanged. */ + return true; +} + /* Save stat information about a regular file for future invocations. */ void StatCache::Save(const string &path, struct stat *stat_buf, const string &checksum, const list &blocks) { + /* Was this file in the old stat cache, and is the information unchanged? + * If so, mark the information "validated", which means we are confident + * that we can use it to accurately detect changes. (Stat information may + * not be updated if, for example, there are two writes within a single + * second and we happen to make the first stat call between them. However, + * if two stat calls separated in time agree, then we will trust the + * values.) */ + bool validated = false; + if (!end_of_cache && path == old_name) { + if (stat_buf->st_mtime == old_mtime + && stat_buf->st_ctime == old_ctime + && (long long)stat_buf->st_ino == old_inode + && old_checksum == checksum) + validated = true; + } + *newcache << uri_encode(path) << "\n"; *newcache << "mtime: " << encode_int(stat_buf->st_mtime) << "\n" << "ctime: " << encode_int(stat_buf->st_ctime) << "\n" << "inode: " << encode_int(stat_buf->st_ino) << "\n" + << "size: " << encode_int(stat_buf->st_size) << "\n" << "checksum: " << checksum << "\n"; *newcache << "blocks:"; @@ -77,5 +271,8 @@ void StatCache::Save(const string &path, struct stat *stat_buf, *newcache << " " << *i << "\n"; } + if (validated) + *newcache << "validated: true\n"; + *newcache << "\n"; }