Skip to content

Commit 9af098c

Browse files
committed
Merge branch 'ym/fix-opportunistic-index-update-race'
Read-only operations such as "git status" that internally refreshes the index write out the refreshed index to the disk to optimize future accesses to the working tree, but this could race with a "read-write" operation that modify the index while it is running. Detect such a race and avoid overwriting the index. Duy raised a good point that we may need to do the same for the normal writeout codepath, not just the "opportunistic" update codepath. While that is true, nobody sane would be running two simultaneous operations that are clearly write-oriented competing with each other against the same index file. So in that sense that can be done as a less urgent follow-up for this topic. * ym/fix-opportunistic-index-update-race: read-cache.c: verify index file before we opportunistically update it wrapper.c: add xpread() similar to xread()
2 parents 2cc70ce + 426ddee commit 9af098c

6 files changed

Lines changed: 90 additions & 5 deletions

File tree

builtin/index-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ static void *unpack_data(struct object_entry *obj,
542542

543543
do {
544544
ssize_t n = (len < 64*1024) ? len : 64*1024;
545-
n = pread(pack_fd, inbuf, n, from);
545+
n = xpread(pack_fd, inbuf, n, from);
546546
if (n < 0)
547547
die_errno(_("cannot pread pack file"));
548548
if (!n)

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ struct index_state {
294294
initialized : 1;
295295
struct hashmap name_hash;
296296
struct hashmap dir_hash;
297+
unsigned char sha1[20];
297298
};
298299

299300
extern struct index_state the_index;
@@ -1337,6 +1338,8 @@ extern void fsync_or_die(int fd, const char *);
13371338

13381339
extern ssize_t read_in_full(int fd, void *buf, size_t count);
13391340
extern ssize_t write_in_full(int fd, const void *buf, size_t count);
1341+
extern ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
1342+
13401343
static inline ssize_t write_str_in_full(int fd, const char *str)
13411344
{
13421345
return write_in_full(fd, str, strlen(str));

compat/mmap.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
1414
}
1515

1616
while (n < length) {
17-
ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
17+
ssize_t count = xpread(fd, (char *)start + n, length - n, offset + n);
1818

1919
if (count == 0) {
2020
memset((char *)start+n, 0, length-n);
2121
break;
2222
}
2323

2424
if (count < 0) {
25-
if (errno == EAGAIN || errno == EINTR)
26-
continue;
2725
free(start);
2826
errno = EACCES;
2927
return MAP_FAILED;

git-compat-util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ extern void *xcalloc(size_t nmemb, size_t size);
539539
extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
540540
extern ssize_t xread(int fd, void *buf, size_t len);
541541
extern ssize_t xwrite(int fd, const void *buf, size_t len);
542+
extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
542543
extern int xdup(int fd);
543544
extern FILE *xfdopen(int fd, const char *mode);
544545
extern int xmkstemp(char *template);

read-cache.c

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,7 @@ int read_index_from(struct index_state *istate, const char *path)
14771477
if (verify_hdr(hdr, mmap_size) < 0)
14781478
goto unmap;
14791479

1480+
hashcpy(istate->sha1, (unsigned char *)hdr + mmap_size - 20);
14801481
istate->version = ntohl(hdr->hdr_version);
14811482
istate->cache_nr = ntohl(hdr->hdr_entries);
14821483
istate->cache_alloc = alloc_nr(istate->cache_nr);
@@ -1760,6 +1761,50 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
17601761
return result;
17611762
}
17621763

1764+
/*
1765+
* This function verifies if index_state has the correct sha1 of the
1766+
* index file. Don't die if we have any other failure, just return 0.
1767+
*/
1768+
static int verify_index_from(const struct index_state *istate, const char *path)
1769+
{
1770+
int fd;
1771+
ssize_t n;
1772+
struct stat st;
1773+
unsigned char sha1[20];
1774+
1775+
if (!istate->initialized)
1776+
return 0;
1777+
1778+
fd = open(path, O_RDONLY);
1779+
if (fd < 0)
1780+
return 0;
1781+
1782+
if (fstat(fd, &st))
1783+
goto out;
1784+
1785+
if (st.st_size < sizeof(struct cache_header) + 20)
1786+
goto out;
1787+
1788+
n = pread_in_full(fd, sha1, 20, st.st_size - 20);
1789+
if (n != 20)
1790+
goto out;
1791+
1792+
if (hashcmp(istate->sha1, sha1))
1793+
goto out;
1794+
1795+
close(fd);
1796+
return 1;
1797+
1798+
out:
1799+
close(fd);
1800+
return 0;
1801+
}
1802+
1803+
static int verify_index(const struct index_state *istate)
1804+
{
1805+
return verify_index_from(istate, get_index_file());
1806+
}
1807+
17631808
static int has_racy_timestamp(struct index_state *istate)
17641809
{
17651810
int entries = istate->cache_nr;
@@ -1779,7 +1824,7 @@ static int has_racy_timestamp(struct index_state *istate)
17791824
void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
17801825
{
17811826
if ((istate->cache_changed || has_racy_timestamp(istate)) &&
1782-
!write_index(istate, lockfile->fd))
1827+
verify_index(istate) && !write_index(istate, lockfile->fd))
17831828
commit_locked_index(lockfile);
17841829
else
17851830
rollback_lock_file(lockfile);

wrapper.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ ssize_t xwrite(int fd, const void *buf, size_t len)
174174
}
175175
}
176176

177+
/*
178+
* xpread() is the same as pread(), but it automatically restarts pread()
179+
* operations with a recoverable error (EAGAIN and EINTR). xpread() DOES
180+
* NOT GUARANTEE that "len" bytes is read even if the data is available.
181+
*/
182+
ssize_t xpread(int fd, void *buf, size_t len, off_t offset)
183+
{
184+
ssize_t nr;
185+
if (len > MAX_IO_SIZE)
186+
len = MAX_IO_SIZE;
187+
while (1) {
188+
nr = pread(fd, buf, len, offset);
189+
if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
190+
continue;
191+
return nr;
192+
}
193+
}
194+
177195
ssize_t read_in_full(int fd, void *buf, size_t count)
178196
{
179197
char *p = buf;
@@ -214,6 +232,26 @@ ssize_t write_in_full(int fd, const void *buf, size_t count)
214232
return total;
215233
}
216234

235+
ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset)
236+
{
237+
char *p = buf;
238+
ssize_t total = 0;
239+
240+
while (count > 0) {
241+
ssize_t loaded = xpread(fd, p, count, offset);
242+
if (loaded < 0)
243+
return -1;
244+
if (loaded == 0)
245+
return total;
246+
count -= loaded;
247+
p += loaded;
248+
total += loaded;
249+
offset += loaded;
250+
}
251+
252+
return total;
253+
}
254+
217255
int xdup(int fd)
218256
{
219257
int ret = dup(fd);

0 commit comments

Comments
 (0)