From: Bradford C. Smith <bradford.carl.smith@xxxxxxxxx> Without this fix, the lockfile code will replace a symlink with a real file. Signed-off-by: "Bradford C. Smith" <bradford.carl.smith@xxxxxxxxx> --- lockfile.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 86 insertions(+), 1 deletions(-) diff --git a/lockfile.c b/lockfile.c index fb8f13b..4c35224 100644 --- a/lockfile.c +++ b/lockfile.c @@ -25,10 +25,95 @@ static void remove_lock_file_on_signal(int signo) raise(signo); } +/** + * p = absolute or relative path name + * + * Return a pointer into p showing the beginning of the last path name + * element. If p is empty or the root directory ("/"), just return p. + */ +static char * last_path_elm(char * p) +{ + int p_len = strlen(p); + char * r; + + if (p_len < 1) return p; + /* r points to last non-null character in p */ + r = p + p_len - 1; + /* first skip any trailing slashes */ + while (*r == '/' && r > p) r--; + /* then go back to the first non-slash */ + while (r > p && *(r-1) != '/') r--; + return r; +} + +/** + * p = char array containing path to existing file or symlink + * s = size of p + * + * If p indicates a valid symlink to an existing file, overwrite p with + * the path to the real file. Otherwise, leave p unmodified. + * + * Always returns p in any case. + * + * NOTE: This is a best-effort routine. It will give no indication of + * failure if it is unable to fully resolve p. However, it is + * guaranteed to leave p in one of the following states if there isn't + * enough room in p or some other failure occurs: + * + * 1. unmodified + * OR + * 2. path to a different symlink in a chain that eventually leads to a + * real file or directory. + */ +static char * resolve_symlink(char * p, size_t s) +{ + struct stat st; + char link[PATH_MAX]; + int link_len; + + /* To avoid an infinite loop of symlinks, try a normal stat() + * first. This will fail if p is a symlink that cannot be + * resolved, so we won't waste our time following a bad link. */ + if (stat(p, &st)) return p; + /* if I can stat() the file, I sure ought to be able to lstat() + * it, but if something bizarre happens, just return p. */ + if (lstat(p, &st)) return p; + /* if not a link, return p unmodified */ + if (!S_ISLNK(st.st_mode)) return p; + link_len = st.st_size; + /* link is too big, so just return p */ + if (link_len >= sizeof(link)) return p; + /* fail if readlink fails, and just return p */ + if (link_len != readlink(p, link, sizeof(link))) return p; + /* readlink never null-terminates */ + link[link_len] = '\0'; + if (link[0] == '/') { + /* absolute path simply replaces p */ + /* fail if link won't fit in p */ + if (link_len >= s) return p; + strcpy(p, link); + } else { + /* link is relative path, so we must replace the last + * element of p with it. */ + char * r = last_path_elm(p); + /* make sure there's room in p for us to replace the + * last element with the link contents */ + if (r - p + link_len >= s) return p; + strcpy(r, link); + } + /* try again in case we've resolved to another symlink */ + return resolve_symlink(p, s); +} + static int lock_file(struct lock_file *lk, const char *path) { int fd; - sprintf(lk->filename, "%s.lock", path); + if (strlen(path) >= sizeof(lk->filename)) return -1; + strcpy(lk->filename, path); + /* subtract 5 from size to make sure there's room for adding + * ".lock" for the lock file name */ + resolve_symlink(lk->filename, sizeof(lk->filename)-5); + strcat(lk->filename, ".lock"); fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); if (0 <= fd) { if (!lock_file_list) { -- 1.5.3.rc2.30.g1c06-dirty - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html