ccx-utils

Miscellaneous utilities written in C
git clone https://ccx.te2000.cz/git/ccx-utils
Log | Files | Refs

safelink.c (3612B)


      1 #include <errno.h>  /* for errno */
      2 #include <fcntl.h>  /* for mkdirat() openat() */
      3 #include <unistd.h>  /* for linkat() */
      4 #include <stdio.h>  /* for renameat() */
      5 #include <stdbool.h>  /* for bool */
      6 
      7 #include <skalibs/strerr.h>
      8 #include <skalibs/random.h>
      9 
     10 #define PROG "safelink"
     11 #define USAGE "safelink oldpath newpath"
     12 #define BUFLEN 260
     13 
     14 const char* opendir_nofollow(const char *pathname, int *fd, bool create_dirs)
     15 {
     16   char next[BUFLEN];
     17   int fd1, fd2;
     18   const char *base;  /* final part (basename) to return */
     19   const char *s1 = pathname;
     20   char *s2 = next;
     21 
     22   if(pathname[0] == '/') {
     23     fd1 = open("/", O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
     24     if(fd1 < 0) {
     25       strerr_diefu1sys(111, "open() root");
     26     }
     27     s1++;
     28   } else {
     29     fd1 = open(".", O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
     30     if(fd1 < 0) {
     31       strerr_diefu1sys(111, "open() CWD");
     32     }
     33   }
     34   base = s1;
     35   while (*s1) {
     36     /* Copy character by character from pathname[] to next[]. */
     37     *(s2++) = *(s1++);
     38     if (s2 >= &next[BUFLEN]) {
     39       strerr_dief1x(100, "filename exceeded buffer size");
     40     }
     41     if(*s1 == 0) {
     42       /* end of string, return dir fd and final path component */
     43       *fd = fd1;
     44       return base;
     45     }
     46     if(*s1 == '/') {
     47       if(*s1) { s1++; }
     48       if(s2 == next) { continue; }  /* skip empty filename or trailing slash */
     49       *s2 = 0;  /* null-terminate the string in next[] */
     50       s2 = next;  /* reset s2 pointer to the start of next[] for next dirname */
     51 
     52       /* Try opening the directory in next[]. */
     53       fd2 = openat(fd1, next, O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
     54       if(fd2 < 1) {
     55         if(errno == ENOENT) {
     56           if(!create_dirs) {
     57             strerr_dief2x(111, "directory does not exist: ", next);
     58           }
     59           /* Create the missing directory. */
     60           if(mkdirat(fd1, next, 0777) != 0) {
     61             strerr_diefu2sys(111, "mkdirat(): ", next);
     62           }
     63           /* Open the newly created directory. */
     64           fd2 = openat(fd1, next, O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
     65           if(fd2 < 1) {
     66             strerr_diefu2sys(111, "openat(): ", next);
     67           }
     68         } else {
     69           strerr_diefu2sys(111, "openat(): ", next);
     70         }
     71       }
     72 
     73       /* Close the parent directory and replace reference to it with the newly opened one. */
     74       close(fd1);
     75       fd1 = fd2;
     76       base = s1;
     77     }
     78   }
     79 }
     80 
     81 int main (int argc, char const *const *argv)
     82 {
     83   if (argc != 3) {
     84     strerr_dieusage(100, USAGE);
     85   }
     86   int old_fd, new_fd;
     87   const char *old_base, *new_base;
     88   old_base = opendir_nofollow(argv[1], &old_fd, false);
     89   if(old_base[0] == 0) {
     90       strerr_dief2x(100, "malformed path: ", argv[1]);
     91   }
     92   new_base = opendir_nofollow(argv[2], &new_fd, true);
     93   if(new_base[0] == 0) {
     94       strerr_dief2x(100, "malformed path: ", argv[2]);
     95   }
     96   if(linkat(old_fd, old_base, new_fd, new_base, 0) == 0) {
     97     return 0; /* created hardlink at the correct location */
     98   }
     99   if(errno != EEXIST) {
    100     strerr_diefu1sys(111, "linkat()");
    101   }
    102   /* file with such name already exists, so try again with different one and atomically replace */
    103   char tmp_base[BUFLEN];
    104   tmp_base[0] = '.';
    105   tmp_base[1] = 't';
    106   tmp_base[2] = 'm';
    107   tmp_base[3] = 'p';
    108   tmp_base[4] = '.';
    109   random_name(&tmp_base[5], 58);
    110   tmp_base[64] = 0;
    111   if(linkat(old_fd, old_base, new_fd, tmp_base, 0) != 0) {
    112     strerr_diefu2sys(111, "linkat() to temporary name: ", tmp_base);
    113   }
    114   if(renameat(new_fd, tmp_base, new_fd, new_base) != 0) {
    115     strerr_diefu4sys(111, "renameat() from temporary name: ", tmp_base, " to:", new_base);
    116   }
    117   return 0;
    118 }
    119 /* vim: sw=2 sts=2 et
    120 */