Skip to content

Commit 1881d2b

Browse files
committed
Merge branch 'ym/fix-opportunistic-index-update-race' into maint
"git status", even though it is a read-only operation, tries to update the index with refreshed lstat(2) info to optimize future accesses to the working tree opportunistically, 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. * 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 85785df + 426ddee commit 1881d2b

File tree

6 files changed

+90
-5
lines changed

6 files changed

+90
-5
lines changed

builtin/index-pack.c

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

555555
do {
556556
ssize_t n = (len < 64*1024) ? len : 64*1024;
557-
n = pread(get_thread_data()->pack_fd, inbuf, n, from);
557+
n = xpread(get_thread_data()->pack_fd, inbuf, n, from);
558558
if (n < 0)
559559
die_errno(_("cannot pread pack file"));
560560
if (!n)

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ struct index_state {
279279
initialized : 1;
280280
struct hashmap name_hash;
281281
struct hashmap dir_hash;
282+
unsigned char sha1[20];
282283
};
283284

284285
extern struct index_state the_index;
@@ -1322,6 +1323,8 @@ extern void fsync_or_die(int fd, const char *);
13221323

13231324
extern ssize_t read_in_full(int fd, void *buf, size_t count);
13241325
extern ssize_t write_in_full(int fd, const void *buf, size_t count);
1326+
extern ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
1327+
13251328
static inline ssize_t write_str_in_full(int fd, const char *str)
13261329
{
13271330
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
@@ -531,6 +531,7 @@ extern void *xcalloc(size_t nmemb, size_t size);
531531
extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
532532
extern ssize_t xread(int fd, void *buf, size_t len);
533533
extern ssize_t xwrite(int fd, const void *buf, size_t len);
534+
extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
534535
extern int xdup(int fd);
535536
extern FILE *xfdopen(int fd, const char *mode);
536537
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)