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 */