Skip to content

Commit

Permalink
Merge branch 'mh/ref-races'
Browse files Browse the repository at this point in the history
"git pack-refs" that races with new ref creation or deletion have
been susceptible to lossage of refs under right conditions, which
has been tightened up.

* mh/ref-races:
  for_each_ref: load all loose refs before packed refs
  get_packed_ref_cache: reload packed-refs file when it changes
  add a stat_validity struct
  Extract a struct stat_data from cache_entry
  packed_ref_cache: increment refcount when locked
  do_for_each_entry(): increment the packed refs cache refcount
  refs: manage lifetime of packed refs cache via reference counting
  refs: implement simple transactions for the packed-refs file
  refs: wrap the packed refs cache in a level of indirection
  pack_refs(): split creation of packed refs and entry writing
  repack_without_ref(): split list curation and entry writing
  • Loading branch information
gitster committed Jun 30, 2013
2 parents 08585fd + 98eeb09 commit 079424a
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 134 deletions.
5 changes: 4 additions & 1 deletion builtin/clone.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;

lock_packed_refs(LOCK_DIE_ON_ERROR);

for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
add_packed_ref(r->peer_ref->name, r->old_sha1);
}

pack_refs(PACK_REFS_ALL);
if (commit_packed_refs())
die_errno("unable to overwrite old ref-pack file");
}

static void write_followtags(const struct ref *refs, const char *msg)
Expand Down
12 changes: 7 additions & 5 deletions builtin/ls-files.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,13 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
}
write_name(ce->name, ce_namelen(ce));
if (debug_mode) {
printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
struct stat_data *sd = &ce->ce_stat_data;

printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}

Expand Down
60 changes: 53 additions & 7 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,19 @@ struct cache_time {
unsigned int nsec;
};

struct stat_data {
struct cache_time sd_ctime;
struct cache_time sd_mtime;
unsigned int sd_dev;
unsigned int sd_ino;
unsigned int sd_uid;
unsigned int sd_gid;
unsigned int sd_size;
};

struct cache_entry {
struct cache_time ce_ctime;
struct cache_time ce_mtime;
unsigned int ce_dev;
unsigned int ce_ino;
struct stat_data ce_stat_data;
unsigned int ce_mode;
unsigned int ce_uid;
unsigned int ce_gid;
unsigned int ce_size;
unsigned int ce_flags;
unsigned int ce_namelen;
unsigned char sha1[20];
Expand Down Expand Up @@ -511,6 +515,21 @@ extern int limit_pathspec_to_literal(void);
#define HASH_FORMAT_CHECK 2
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);

/*
* Record to sd the data from st that we use to check whether a file
* might have changed.
*/
extern void fill_stat_data(struct stat_data *sd, struct stat *st);

/*
* Return 0 if st is consistent with a file not having been changed
* since sd was filled. If there are differences, return a
* combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
* INODE_CHANGED, and DATA_CHANGED.
*/
extern int match_stat_data(const struct stat_data *sd, struct stat *st);

extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);

#define REFRESH_REALLY 0x0001 /* ignore_valid */
Expand Down Expand Up @@ -1338,4 +1357,31 @@ int checkout_fast_forward(const unsigned char *from,

int sane_execvp(const char *file, char *const argv[]);

/*
* A struct to encapsulate the concept of whether a file has changed
* since we last checked it. This uses criteria similar to those used
* for the index.
*/
struct stat_validity {
struct stat_data *sd;
};

void stat_validity_clear(struct stat_validity *sv);

/*
* Returns 1 if the path is a regular file (or a symlink to a regular
* file) and matches the saved stat_validity, 0 otherwise. A missing
* or inaccessible file is considered a match if the struct was just
* initialized, or if the previous update found an inaccessible file.
*/
int stat_validity_check(struct stat_validity *sv, const char *path);

/*
* Update the stat_validity from a file opened at descriptor fd. If
* the file is missing, inaccessible, or not a regular file, then
* future calls to stat_validity_check will match iff one of those
* conditions continues to be true.
*/
void stat_validity_update(struct stat_validity *sv, int fd);

#endif /* CACHE_H */
181 changes: 113 additions & 68 deletions read-cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,69 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}

void fill_stat_data(struct stat_data *sd, struct stat *st)
{
sd->sd_ctime.sec = (unsigned int)st->st_ctime;
sd->sd_mtime.sec = (unsigned int)st->st_mtime;
sd->sd_ctime.nsec = ST_CTIME_NSEC(*st);
sd->sd_mtime.nsec = ST_MTIME_NSEC(*st);
sd->sd_dev = st->st_dev;
sd->sd_ino = st->st_ino;
sd->sd_uid = st->st_uid;
sd->sd_gid = st->st_gid;
sd->sd_size = st->st_size;
}

int match_stat_data(const struct stat_data *sd, struct stat *st)
{
int changed = 0;

if (sd->sd_mtime.sec != (unsigned int)st->st_mtime)
changed |= MTIME_CHANGED;
if (trust_ctime && check_stat &&
sd->sd_ctime.sec != (unsigned int)st->st_ctime)
changed |= CTIME_CHANGED;

#ifdef USE_NSEC
if (check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
changed |= MTIME_CHANGED;
if (trust_ctime && check_stat &&
sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
changed |= CTIME_CHANGED;
#endif

if (check_stat) {
if (sd->sd_uid != (unsigned int) st->st_uid ||
sd->sd_gid != (unsigned int) st->st_gid)
changed |= OWNER_CHANGED;
if (sd->sd_ino != (unsigned int) st->st_ino)
changed |= INODE_CHANGED;
}

#ifdef USE_STDEV
/*
* st_dev breaks on network filesystems where different
* clients will have different views of what "device"
* the filesystem is on
*/
if (check_stat && sd->sd_dev != (unsigned int) st->st_dev)
changed |= INODE_CHANGED;
#endif

if (sd->sd_size != (unsigned int) st->st_size)
changed |= DATA_CHANGED;

return changed;
}

/*
* This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used
* to validate the cache.
*/
void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
{
ce->ce_ctime.sec = (unsigned int)st->st_ctime;
ce->ce_mtime.sec = (unsigned int)st->st_mtime;
ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
ce->ce_dev = st->st_dev;
ce->ce_ino = st->st_ino;
ce->ce_uid = st->st_uid;
ce->ce_gid = st->st_gid;
ce->ce_size = st->st_size;
fill_stat_data(&ce->ce_stat_data, st);

if (assume_unchanged)
ce->ce_flags |= CE_VALID;
Expand Down Expand Up @@ -195,43 +242,11 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
default:
die("internal error: ce_mode is %o", ce->ce_mode);
}
if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
changed |= MTIME_CHANGED;
if (trust_ctime && check_stat &&
ce->ce_ctime.sec != (unsigned int)st->st_ctime)
changed |= CTIME_CHANGED;

#ifdef USE_NSEC
if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
changed |= MTIME_CHANGED;
if (trust_ctime && check_stat &&
ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
changed |= CTIME_CHANGED;
#endif

if (check_stat) {
if (ce->ce_uid != (unsigned int) st->st_uid ||
ce->ce_gid != (unsigned int) st->st_gid)
changed |= OWNER_CHANGED;
if (ce->ce_ino != (unsigned int) st->st_ino)
changed |= INODE_CHANGED;
}

#ifdef USE_STDEV
/*
* st_dev breaks on network filesystems where different
* clients will have different views of what "device"
* the filesystem is on
*/
if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
changed |= INODE_CHANGED;
#endif

if (ce->ce_size != (unsigned int) st->st_size)
changed |= DATA_CHANGED;
changed |= match_stat_data(&ce->ce_stat_data, st);

/* Racily smudged entry? */
if (!ce->ce_size) {
if (!ce->ce_stat_data.sd_size) {
if (!is_empty_blob_sha1(ce->sha1))
changed |= DATA_CHANGED;
}
Expand All @@ -246,11 +261,11 @@ static int is_racy_timestamp(const struct index_state *istate,
istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
(istate->timestamp.sec < ce->ce_mtime.sec ||
(istate->timestamp.sec == ce->ce_mtime.sec &&
istate->timestamp.nsec <= ce->ce_mtime.nsec))
(istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
(istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
#else
istate->timestamp.sec <= ce->ce_mtime.sec
istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
#endif
);
}
Expand Down Expand Up @@ -342,7 +357,7 @@ int ie_modified(const struct index_state *istate,
* then we know it is.
*/
if ((changed & DATA_CHANGED) &&
(S_ISGITLINK(ce->ce_mode) || ce->ce_size != 0))
(S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
return changed;

changed_fs = ce_modified_check_fs(ce, st);
Expand Down Expand Up @@ -1324,16 +1339,16 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on
{
struct cache_entry *ce = xmalloc(cache_entry_size(len));

ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
ce->ce_dev = ntoh_l(ondisk->dev);
ce->ce_ino = ntoh_l(ondisk->ino);
ce->ce_stat_data.sd_ctime.sec = ntoh_l(ondisk->ctime.sec);
ce->ce_stat_data.sd_mtime.sec = ntoh_l(ondisk->mtime.sec);
ce->ce_stat_data.sd_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
ce->ce_stat_data.sd_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
ce->ce_stat_data.sd_dev = ntoh_l(ondisk->dev);
ce->ce_stat_data.sd_ino = ntoh_l(ondisk->ino);
ce->ce_mode = ntoh_l(ondisk->mode);
ce->ce_uid = ntoh_l(ondisk->uid);
ce->ce_gid = ntoh_l(ondisk->gid);
ce->ce_size = ntoh_l(ondisk->size);
ce->ce_stat_data.sd_uid = ntoh_l(ondisk->uid);
ce->ce_stat_data.sd_gid = ntoh_l(ondisk->gid);
ce->ce_stat_data.sd_size = ntoh_l(ondisk->size);
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
hashcpy(ce->sha1, ondisk->sha1);
Expand Down Expand Up @@ -1611,7 +1626,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
* The only thing we care about in this function is to smudge the
* falsely clean entry due to touch-update-touch race, so we leave
* everything else as they are. We are called for entries whose
* ce_mtime match the index file mtime.
* ce_stat_data.sd_mtime match the index file mtime.
*
* Note that this actually does not do much for gitlinks, for
* which ce_match_stat_basic() always goes to the actual
Expand Down Expand Up @@ -1650,7 +1665,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
* file, and never calls us, so the cached size information
* for "frotz" stays 6 which does not match the filesystem.
*/
ce->ce_size = 0;
ce->ce_stat_data.sd_size = 0;
}
}

Expand All @@ -1660,16 +1675,16 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
{
short flags;

ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
ondisk->dev = htonl(ce->ce_dev);
ondisk->ino = htonl(ce->ce_ino);
ondisk->ctime.sec = htonl(ce->ce_stat_data.sd_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_stat_data.sd_mtime.sec);
ondisk->ctime.nsec = htonl(ce->ce_stat_data.sd_ctime.nsec);
ondisk->mtime.nsec = htonl(ce->ce_stat_data.sd_mtime.nsec);
ondisk->dev = htonl(ce->ce_stat_data.sd_dev);
ondisk->ino = htonl(ce->ce_stat_data.sd_ino);
ondisk->mode = htonl(ce->ce_mode);
ondisk->uid = htonl(ce->ce_uid);
ondisk->gid = htonl(ce->ce_gid);
ondisk->size = htonl(ce->ce_size);
ondisk->uid = htonl(ce->ce_stat_data.sd_uid);
ondisk->gid = htonl(ce->ce_stat_data.sd_gid);
ondisk->size = htonl(ce->ce_stat_data.sd_size);
hashcpy(ondisk->sha1, ce->sha1);

flags = ce->ce_flags;
Expand Down Expand Up @@ -1936,3 +1951,33 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un
*size = sz;
return data;
}

void stat_validity_clear(struct stat_validity *sv)
{
free(sv->sd);
sv->sd = NULL;
}

int stat_validity_check(struct stat_validity *sv, const char *path)
{
struct stat st;

if (stat(path, &st) < 0)
return sv->sd == NULL;
if (!sv->sd)
return 0;
return S_ISREG(st.st_mode) && !match_stat_data(sv->sd, &st);
}

void stat_validity_update(struct stat_validity *sv, int fd)
{
struct stat st;

if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode))
stat_validity_clear(sv);
else {
if (!sv->sd)
sv->sd = xcalloc(1, sizeof(struct stat_data));
fill_stat_data(sv->sd, &st);
}
}
Loading

0 comments on commit 079424a

Please sign in to comment.