# # # patch "ChangeLog" # from [9201a20f06e7485e50b7f2355c57ca3b8e46fda5] # to [293183871c3cbe2c818712f198d2df2c0428f68b] # # patch "unix/inodeprint.cc" # from [97d9fd62fa12374cde1be2c31269793730532084] # to [69978997d67e2cc2e60e4fc2c1ba0a6179348525] # ============================================================ --- ChangeLog 9201a20f06e7485e50b7f2355c57ca3b8e46fda5 +++ ChangeLog 293183871c3cbe2c818712f198d2df2c0428f68b @@ -1,3 +1,10 @@ +2006-07-06 Nathaniel Smith + + * unix/inodeprint.cc (inodeprint_file): Add some clever logic + inspired by Martin Pool, to be somewhat more robust against clock + issues. This should be ported to win32, but I don't know how; I + also have no earthly idea how to test this new code. + 2006-07-05 Nathaniel Smith * tests/checking_a_few_command_specific_options/__driver__.lua ============================================================ --- unix/inodeprint.cc 97d9fd62fa12374cde1be2c31269793730532084 +++ unix/inodeprint.cc 69978997d67e2cc2e60e4fc2c1ba0a6179348525 @@ -1,9 +1,10 @@ // copyright (C) 2005 nathaniel smith // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details #include +#include #include "botan/botan.h" #include "botan/sha160.h" @@ -29,15 +30,61 @@ } }; +// To make this more robust, there are some tricks: +// -- we refuse to inodeprint files whose times are within a few seconds of +// 'now'. This is because, we might memorize the inodeprint, then +// someone writes to the file, and this write does not update the +// timestamp -- or rather, it does update the timestamp, but nothing +// happens, because the new value is the same as the old value. We use +// "a few seconds" to make sure that it is larger than whatever the +// filesystem's timekeeping granularity is (rounding to 2 seconds is +// known to exist in the wild). +// -- by the same reasoning, we should also refuse to inodeprint files whose +// time is in the future, because it is possible that someone will write +// to that file exactly when that future second arrives, and we will +// never notice. However, this would create persistent and hard to +// diagnosis slowdowns, whenever a tree accidentally had its times set +// into the future. Therefore, to handle this case, we include a "is +// this time in the future?" bit in the hashed information. This bit +// will change when we pass the future point, and trigger a re-check of +// the file's contents. +// +// This is, of course, still not perfect. There is no way to make our stat +// atomic with the actual read of the file, so there's always a race condition +// there. Additionally, this handling means that checkout will never actually +// inodeprint anything, but rather the first command after checkout will be +// slow. There doesn't seem to be anything that could be done about this. + +inline bool should_abort(time_t now, time_t then) +{ + if (now < 0 || then < 0) + return false; + double difference = difftime(now, then); + return (difference >= -3 && difference <= 3); +} + +inline bool is_future(time_t now, time_t then) +{ + if (now < 0 || then < 0) + return false; + return difftime(now, then) > 0; +} + bool inodeprint_file(file_path const & file, hexenc & ip) { struct stat st; if (stat(file.as_external().c_str(), &st) < 0) return false; + time_t now; + time(&now); + Botan::SHA_160 hash; + if (should_abort(now, st.st_ctime)) + return false; add_hash(hash, st.st_ctime); + add_hash(hash, is_future(now, st.st_ctime)); // aah, portability. #ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC @@ -50,7 +97,10 @@ add_hash(hash, (long)0); #endif + if (should_abort(now, st.st_mtime)) + return false; add_hash(hash, st.st_mtime); + add_hash(hash, is_future(now, st.st_mtime)); #ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC add_hash(hash, st.st_mtim.tv_nsec);