@@ -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) {