1 #define _FILE_OFFSET_BITS 64 2 #ifndef _LARGEFILE_SOURCE 3 #define _LARGEFILE_SOURCE 4 #endif 5 #ifndef _LARGEFILE64_SOURCE 6 #define _LARGEFILE64_SOURCE 7 #endif 8 9 #include <unistd.h> 10 #ifndef _POSIX_SOURCE 11 #define _POSIX_SOURCE 12 #endif 13 #include <stdio.h> 14 #include <stdlib.h> 15 #ifdef HAVE_MALLOC_H 16 #include <malloc.h> 17 #endif 18 #include <string.h> 19 #include <fcntl.h> 20 #include <sys/param.h> 21 #include <sys/types.h> 22 #include <sys/stat.h> 23 #include <dirent.h> 24 #include <time.h> 25 #include <stddef.h> 26 #include <errno.h> 27 28 #ifndef S_ISLNK 29 #define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) 30 #endif 31 32 #ifndef PATH_MAX 33 #define PATH_MAX 1024 34 #endif 35 36 #define progver "%s: scan/change symbolic links - v1.3 - by Mark Lord\n\n" 37 static char *progname; 38 static int verbose = 0, fix_links = 0, recurse = 0, delete = 0, shorten = 0, 39 testing = 0, single_fs = 1; 40 41 /* 42 * tidypath removes excess slashes and "." references from a path string 43 */ 44 45 static int substr (char *s, char *old, char *new) 46 { 47 char *tmp = NULL; 48 int oldlen = strlen(old), newlen = 0; 49 50 if (NULL == strstr(s, old)) 51 return 0; 52 53 if (new) 54 newlen = strlen(new); 55 56 if (newlen > oldlen) { 57 if ((tmp = malloc(strlen(s))) == NULL) { 58 fprintf(stderr, "no memory\n"); 59 exit (1); 60 } 61 } 62 63 while (NULL != (s = strstr(s, old))) { 64 char *p, *old_s = s; 65 66 if (new) { 67 if (newlen > oldlen) 68 old_s = strcpy(tmp, s); 69 p = new; 70 while (*p) 71 *s++ = *p++; 72 } 73 p = old_s + oldlen; 74 while ((*s++ = *p++)); 75 } 76 if (tmp) 77 free(tmp); 78 return 1; 79 } 80 81 82 static int tidy_path (char *path) 83 { 84 int tidied = 0; 85 char *s, *p; 86 87 s = path + strlen(path) - 1; 88 if (s[0] != '/') { /* tmp trailing slash simplifies things */ 89 s[1] = '/'; 90 s[2] = '\0'; 91 } 92 while (substr(path, "/./", "/")) 93 tidied = 1; 94 while (substr(path, "//", "/")) 95 tidied = 1; 96 97 while ((p = strstr(path,"/../")) != NULL) { 98 s = p+3; 99 for (p--; p != path; p--) if (*p == '/') break; 100 if (*p != '/') 101 break; 102 while ((*p++ = *s++)); 103 tidied = 1; 104 } 105 if (*path == '\0') 106 strcpy(path,"/"); 107 p = path + strlen(path) - 1; 108 if (p != path && *p == '/') 109 *p-- = '\0'; /* remove tmp trailing slash */ 110 while (p != path && *p == '/') { /* remove any others */ 111 *p-- = '\0'; 112 tidied = 1; 113 } 114 while (!strncmp(path,"./",2)) { 115 for (p = path, s = path+2; (*p++ = *s++);); 116 tidied = 1; 117 } 118 return tidied; 119 } 120 121 static int shorten_path (char *path, char *abspath) 122 { 123 static char dir[PATH_MAX]; 124 int shortened = 0; 125 char *p; 126 127 /* get rid of unnecessary "../dir" sequences */ 128 while (abspath && strlen(abspath) > 1 && (p = strstr(path,"../"))) { 129 /* find innermost occurance of "../dir", and save "dir" */ 130 int slashes = 2; 131 char *a, *s, *d = dir; 132 while ((s = strstr(p+3, "../"))) { 133 ++slashes; 134 p = s; 135 } 136 s = p+3; 137 *d++ = '/'; 138 while (*s && *s != '/') 139 *d++ = *s++; 140 *d++ = '/'; 141 *d = '\0'; 142 if (!strcmp(dir,"//")) 143 break; 144 /* note: p still points at ../dir */ 145 if (*s != '/' || !*++s) 146 break; 147 a = abspath + strlen(abspath) - 1; 148 while (slashes-- > 0) { 149 if (a <= abspath) 150 goto ughh; 151 while (*--a != '/') { 152 if (a <= abspath) 153 goto ughh; 154 } 155 } 156 if (strncmp(dir, a, strlen(dir))) 157 break; 158 while ((*p++ = *s++)); /* delete the ../dir */ 159 shortened = 1; 160 } 161 ughh: 162 return shortened; 163 } 164 165 166 static void fix_symlink (char *path, dev_t my_dev) 167 { 168 static char lpath[PATH_MAX], new[PATH_MAX], abspath[PATH_MAX]; 169 char *p, *np, *lp, *tail, *msg; 170 struct stat stbuf, lstbuf; 171 int c, fix_abs = 0, fix_messy = 0, fix_long = 0; 172 173 if ((c = readlink(path, lpath, sizeof(lpath) - 1)) == -1) { 174 perror(path); 175 return; 176 } 177 lpath[c] = '\0'; /* readlink does not null terminate it */ 178 179 /* construct the absolute address of the link */ 180 abspath[0] = '\0'; 181 if (lpath[0] != '/') { 182 strcat(abspath,path); 183 c = strlen(abspath); 184 if ((c > 0) && (abspath[c-1] == '/')) 185 abspath[c-1] = '\0'; /* cut trailing / */ 186 if ((p = strrchr(abspath,'/')) != NULL) 187 *p = '\0'; /* cut last component */ 188 strcat(abspath,"/"); 189 } 190 strcat(abspath,lpath); 191 (void) tidy_path(abspath); 192 193 /* check for various things */ 194 if (stat(abspath, &stbuf) == -1) { 195 printf("dangling: %s -> %s\n", path, lpath); 196 if (delete) { 197 if (unlink (path)) { 198 perror(path); 199 } else 200 printf("deleted: %s -> %s\n", path, lpath); 201 } 202 return; 203 } 204 205 if (single_fs) 206 lstat(abspath, &lstbuf); /* if the above didn't fail, then this shouldn't */ 207 208 if (single_fs && lstbuf.st_dev != my_dev) { 209 msg = "other_fs:"; 210 } else if (lpath[0] == '/') { 211 msg = "absolute:"; 212 fix_abs = 1; 213 } else if (verbose) { 214 msg = "relative:"; 215 } else 216 msg = NULL; 217 fix_messy = tidy_path(strcpy(new,lpath)); 218 if (shorten) 219 fix_long = shorten_path(new, path); 220 if (!fix_abs) { 221 if (fix_messy) 222 msg = "messy: "; 223 else if (fix_long) 224 msg = "lengthy: "; 225 } 226 if (msg != NULL) 227 printf("%s %s -> %s\n", msg, path, lpath); 228 if (!(fix_links || testing) || !(fix_messy || fix_abs || fix_long)) 229 return; 230 231 if (fix_abs) { 232 /* convert an absolute link to relative: */ 233 /* point tail at first part of lpath that differs from path */ 234 /* point p at first part of path that differs from lpath */ 235 (void) tidy_path(lpath); 236 tail = lp = lpath; 237 p = path; 238 while (*p && (*p == *lp)) { 239 if (*lp++ == '/') { 240 tail = lp; 241 while (*++p == '/'); 242 } 243 } 244 245 /* now create new, with "../"s followed by tail */ 246 np = new; 247 while (*p) { 248 if (*p++ == '/') { 249 *np++ = '.'; 250 *np++ = '.'; 251 *np++ = '/'; 252 while (*p == '/') ++p; 253 } 254 } 255 strcpy (np, tail); 256 (void) tidy_path(new); 257 if (shorten) (void) shorten_path(new, path); 258 } 259 shorten_path(new,path); 260 if (!testing) { 261 if (unlink (path)) { 262 perror(path); 263 return; 264 } 265 if (symlink(new, path)) { 266 perror(path); 267 return; 268 } 269 } 270 printf("changed: %s -> %s\n", path, new); 271 } 272 273 static void dirwalk (char *path, int pathlen, dev_t dev) 274 { 275 char *name; 276 DIR *dfd; 277 static struct stat st; 278 static struct dirent *dp; 279 280 if ((dfd = opendir(path)) == NULL) { 281 perror(path); 282 return; 283 } 284 285 name = path + pathlen; 286 if (*(name-1) != '/') 287 *name++ = '/'; 288 289 while ((dp = readdir(dfd)) != NULL ) { 290 strcpy(name, dp->d_name); 291 if (strcmp(name, ".") && strcmp(name,"..")) { 292 if (lstat(path, &st) == -1) { 293 perror(path); 294 } else if (st.st_dev == dev) { 295 if (S_ISLNK(st.st_mode)) { 296 fix_symlink (path, dev); 297 } else if (recurse && S_ISDIR(st.st_mode)) { 298 dirwalk(path, strlen(path), dev); 299 } 300 } 301 } 302 } 303 closedir(dfd); 304 path[pathlen] = '\0'; 305 } 306 307 static void usage_error (void) 308 { 309 fprintf(stderr, progver, progname); 310 fprintf(stderr, "Usage:\t%s [-cdorstv] LINK|DIR ...\n\n", progname); 311 fprintf(stderr, "Flags:" 312 "\t-c == change absolute/messy links to relative\n" 313 "\t-d == delete dangling links\n" 314 "\t-o == warn about links across file systems\n" 315 "\t-r == recurse into subdirs\n" 316 "\t-s == shorten lengthy links (displayed in output only when -c not specified)\n" 317 "\t-t == show what would be done by -c\n" 318 "\t-v == verbose (show all symlinks)\n\n"); 319 exit(1); 320 } 321 322 int main(int argc, char **argv) 323 { 324 #if defined (_GNU_SOURCE) && defined (__GLIBC__) 325 static char path[PATH_MAX+2]; 326 char* cwd = get_current_dir_name(); 327 #else 328 static char path[PATH_MAX+2], cwd[PATH_MAX+2]; 329 #endif 330 int dircount = 0; 331 char c, *p; 332 333 if ((progname = (char *) strrchr(*argv, '/')) == NULL) 334 progname = *argv; 335 else 336 progname++; 337 338 #if defined (_GNU_SOURCE) && defined (__GLIBC__) 339 if (NULL == cwd) { 340 fprintf(stderr,"get_current_dir_name() failed\n"); 341 #else 342 if (NULL == getcwd(cwd,PATH_MAX)) { 343 fprintf(stderr,"getcwd() failed\n"); 344 #endif 345 exit (1); 346 } 347 #if defined (_GNU_SOURCE) && defined (__GLIBC__) 348 cwd = realloc(cwd, strlen(cwd)+2); 349 if (cwd == NULL) { 350 fprintf(stderr, "realloc() failed\n"); 351 exit (1); 352 } 353 #endif 354 if (!*cwd || cwd[strlen(cwd)-1] != '/') 355 strcat(cwd,"/"); 356 357 while (--argc) { 358 p = *++argv; 359 if (*p == '-') { 360 if (*++p == '\0') 361 usage_error(); 362 while ((c = *p++)) { 363 if (c == 'c') fix_links = 1; 364 else if (c == 'd') delete = 1; 365 else if (c == 'o') single_fs = 0; 366 else if (c == 'r') recurse = 1; 367 else if (c == 's') shorten = 1; 368 else if (c == 't') testing = 1; 369 else if (c == 'v') verbose = 1; 370 else usage_error(); 371 } 372 } else { 373 struct stat st; 374 if (*p == '/') 375 *path = '\0'; 376 else 377 strcpy(path,cwd); 378 tidy_path(strcat(path, p)); 379 if (lstat(path, &st) == -1) 380 perror(path); 381 else if (S_ISLNK(st.st_mode)) 382 fix_symlink(path, st.st_dev); 383 else 384 dirwalk(path, strlen(path), st.st_dev); 385 ++dircount; 386 } 387 } 388 if (dircount == 0) 389 usage_error(); 390 exit (0); 391 } 392