[PATCH] fix compare symlink against readlink not data
Fix update-cache to compare the blob of a symlink against the link-target
and not the file it points to. Also ignore all permissions applied to
links.
Thanks to Greg for recognizing this while he added our list of symlinks
back to the udev repository.
Signed-off-by: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/diff-files.c b/diff-files.c
index 0cf2c24..c51edc3 100644
--- a/diff-files.c
+++ b/diff-files.c
@@ -111,7 +111,7 @@
continue;
}
- if (stat(ce->name, &st) < 0) {
+ if (lstat(ce->name, &st) < 0) {
if (errno != ENOENT) {
perror(ce->name);
continue;
diff --git a/read-cache.c b/read-cache.c
index 5703f30..2a88d18 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -16,6 +16,9 @@
switch (ntohl(ce->ce_mode) & S_IFMT) {
case S_IFREG:
changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
+ /* We consider only the owner x bit to be relevant for "mode changes" */
+ if (0100 & (ntohl(ce->ce_mode) ^ st->st_mode))
+ changed |= MODE_CHANGED;
break;
case S_IFLNK:
changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
@@ -43,9 +46,6 @@
if (ce->ce_uid != htonl(st->st_uid) ||
ce->ce_gid != htonl(st->st_gid))
changed |= OWNER_CHANGED;
- /* We consider only the owner x bit to be relevant for "mode changes" */
- if (0100 & (ntohl(ce->ce_mode) ^ st->st_mode))
- changed |= MODE_CHANGED;
if (ce->ce_dev != htonl(st->st_dev) ||
ce->ce_ino != htonl(st->st_ino))
changed |= INODE_CHANGED;
diff --git a/update-cache.c b/update-cache.c
index 893ba86..210c786 100644
--- a/update-cache.c
+++ b/update-cache.c
@@ -64,7 +64,7 @@
struct stat st;
int fd;
unsigned int len;
- char target[1024];
+ char *target;
if (lstat(path, &st) < 0) {
if (errno == ENOENT || errno == ENOTDIR) {
@@ -90,11 +90,14 @@
return -1;
break;
case S_IFLNK:
- len = readlink(path, target, sizeof(target));
- if (len == -1 || len+1 > sizeof(target))
+ target = xmalloc(st.st_size+1);
+ if (readlink(path, target, st.st_size+1) != st.st_size) {
+ free(target);
return -1;
- if (write_sha1_file(target, len, "blob", ce->sha1))
+ }
+ if (write_sha1_file(target, st.st_size, "blob", ce->sha1))
return -1;
+ free(target);
break;
default:
return -1;
@@ -163,6 +166,33 @@
return match;
}
+static int compare_link(struct cache_entry *ce, unsigned long expected_size)
+{
+ int match = -1;
+ char *target;
+ void *buffer;
+ unsigned long size;
+ char type[10];
+ int len;
+
+ target = xmalloc(expected_size);
+ len = readlink(ce->name, target, expected_size);
+ if (len != expected_size) {
+ free(target);
+ return -1;
+ }
+ buffer = read_sha1_file(ce->sha1, type, &size);
+ if (!buffer) {
+ free(target);
+ return -1;
+ }
+ if (size == expected_size)
+ match = memcmp(buffer, target, size);
+ free(buffer);
+ free(target);
+ return match;
+}
+
/*
* "refresh" does not calculate a new sha1 file or bring the
* cache up-to-date for mode/content changes. But what it
@@ -194,8 +224,18 @@
if (changed & (MODE_CHANGED | TYPE_CHANGED))
return ERR_PTR(-EINVAL);
- if (compare_data(ce, st.st_size))
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ if (compare_data(ce, st.st_size))
+ return ERR_PTR(-EINVAL);
+ break;
+ case S_IFLNK:
+ if (compare_link(ce, st.st_size))
+ return ERR_PTR(-EINVAL);
+ break;
+ default:
return ERR_PTR(-EINVAL);
+ }
cache_changed = 1;
size = ce_size(ce);