pthbs

Packaging Through Hashed Build Scripts
git clone https://ccx.te2000.cz/git/pthbs
Log | Files | Refs | Submodules | README

safelink.c (3514B)


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