#include #ifndef _POSIX_SOURCE #define _POSIX_SOURCE #endif #include #include #if !defined(__APPLE__) #include #endif #include #include #include #include #include #include #include #include #include #ifndef S_ISLNK #define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) #endif #ifndef PATH_MAX #define PATH_MAX 1024 #endif #define progver "%s: scan/change symbolic links - v1.4.3\n\n" static char *progname; static int verbose = 0, fix_links = 0, recurse = 0, delete = 0, shorten = 0, testing = 0, single_fs = 1; /* * tidypath removes excess slashes and "." references from a path string */ static int substr(char *s, char *old, char *new) { char *tmp = NULL; unsigned long oldlen = strlen(old), newlen = 0; if (NULL == strstr(s, old)) { return 0; } if (new) { newlen = strlen(new); } if (newlen > oldlen) { if ((tmp = malloc(strlen(s))) == NULL) { fprintf(stderr, "no memory\n"); exit(1); } } while (NULL != (s = strstr(s, old))) { char *p, *old_s = s; if (new) { if (newlen > oldlen) { old_s = strcpy(tmp, s); } p = new; while (*p) { *s++ = *p++; } } p = old_s + oldlen; while ((*s++ = *p++)); } if (tmp) { free(tmp); } return 1; } static int tidy_path(char *path) { int tidied = 0; char *s, *p; s = path + strlen(path) - 1; if (s[0] != '/') { /* tmp trailing slash simplifies things */ s[1] = '/'; s[2] = '\0'; } while (substr(path, "/./", "/")) { tidied = 1; } while (substr(path, "//", "/")) { tidied = 1; } while ((p = strstr(path, "/../")) != NULL) { s = p + 3; for (p--; p != path; p--) { if (*p == '/') { break; } } if (*p != '/') { break; } while ((*p++ = *s++)); tidied = 1; } if (*path == '\0') { strcpy(path, "/"); } p = path + strlen(path) - 1; if (p != path && *p == '/') { *p-- = '\0'; /* remove tmp trailing slash */ } while (p != path && *p == '/') { /* remove any others */ *p-- = '\0'; tidied = 1; } while (!strncmp(path, "./", 2)) { for (p = path, s = path + 2; (*p++ = *s++);); tidied = 1; } return tidied; } static int shorten_path(char *path, char *abspath) { static char dir[PATH_MAX]; int shortened = 0; char *p; /* get rid of unnecessary "../dir" sequences */ while (abspath && strlen(abspath) > 1 && (p = strstr(path, "../"))) { /* find innermost occurance of "../dir", and save "dir" */ int slashes = 2; char *a, *s, *d = dir; while ((s = strstr(p + 3, "../"))) { ++slashes; p = s; } s = p + 3; *d++ = '/'; while (*s && *s != '/') { *d++ = *s++; } *d++ = '/'; *d = '\0'; if (!strcmp(dir, "//")) { break; } /* note: p still points at ../dir */ if (*s != '/' || !*++s) { break; } a = abspath + strlen(abspath) - 1; while (slashes-- > 0) { if (a <= abspath) { goto ughh; } while (*--a != '/') { if (a <= abspath) { goto ughh; } } } if (strncmp(dir, a, strlen(dir))) { break; } while ((*p++ = *s++)); /* delete the ../dir */ shortened = 1; } ughh: return shortened; } static void fix_symlink(char *path, dev_t my_dev) { static char lpath[PATH_MAX], new[PATH_MAX], abspath[PATH_MAX]; char *p, *np, *lp, *tail, *msg; struct stat stbuf, lstbuf; int fix_abs = 0, fix_messy = 0, fix_long = 0; size_t c; if ((c = readlink(path, lpath, sizeof(lpath))) == -1) { perror(path); return; } lpath[c] = '\0'; /* readlink does not null terminate it */ /* construct the absolute address of the link */ abspath[0] = '\0'; if (lpath[0] != '/') { strcat(abspath, path); c = strlen(abspath); if ((c > 0) && (abspath[c - 1] == '/')) { abspath[c - 1] = '\0'; /* cut trailing / */ } if ((p = strrchr(abspath, '/')) != NULL) { *p = '\0'; /* cut last component */ } strcat(abspath, "/"); } strcat(abspath, lpath); (void) tidy_path(abspath); /* check for various things */ if (stat(abspath, &stbuf) == -1) { printf("dangling: %s -> %s\n", path, lpath); if (delete) { if (unlink(path)) { perror(path); } else { printf("deleted: %s -> %s\n", path, lpath); } } return; } if (single_fs) { lstat(abspath, &lstbuf); /* if the above didn't fail, then this shouldn't */ } if (single_fs && lstbuf.st_dev != my_dev) { msg = "other_fs:"; } else if (lpath[0] == '/') { msg = "absolute:"; fix_abs = 1; } else if (verbose) { msg = "relative:"; } else { msg = NULL; } fix_messy = tidy_path(strcpy(new, lpath)); if (shorten) { fix_long = shorten_path(new, path); } if (!fix_abs) { if (fix_messy) { msg = "messy: "; } else if (fix_long) { msg = "lengthy: "; } } if (msg != NULL) { printf("%s %s -> %s\n", msg, path, lpath); } if (!(fix_links || testing) || !(fix_messy || fix_abs || fix_long)) { return; } if (fix_abs) { /* convert an absolute link to relative: */ /* point tail at first part of lpath that differs from path */ /* point p at first part of path that differs from lpath */ (void) tidy_path(lpath); tail = lp = lpath; p = path; while (*p && (*p == *lp)) { if (*lp++ == '/') { tail = lp; while (*++p == '/'); } } /* now create new, with "../"s followed by tail */ np = new; while (*p) { if (*p++ == '/') { *np++ = '.'; *np++ = '.'; *np++ = '/'; while (*p == '/') { ++p; } } } strcpy(np, tail); (void) tidy_path(new); if (shorten) { (void) shorten_path(new, path); } } shorten_path(new, path); if (!testing) { if (unlink(path)) { perror(path); return; } if (symlink(new, path)) { perror(path); return; } } printf("changed: %s -> %s\n", path, new); } static void dirwalk(char *path, unsigned long pathlen, dev_t dev) { char *name; DIR *dfd; static struct stat st; static struct dirent *dp; if ((dfd = opendir(path)) == NULL) { perror(path); return; } name = path + pathlen; if (*(name - 1) != '/') { *name++ = '/'; } while ((dp = readdir(dfd)) != NULL) { strcpy(name, dp->d_name); if (strcmp(name, ".") && strcmp(name, "..")) { if (lstat(path, &st) == -1) { perror(path); } else if (st.st_dev == dev) { if (S_ISLNK(st.st_mode)) { fix_symlink(path, dev); } else if (recurse && S_ISDIR(st.st_mode)) { dirwalk(path, strlen(path), dev); } } } } closedir(dfd); path[pathlen] = '\0'; } static void usage_error(void) { fprintf(stderr, progver, progname); fprintf(stderr, "Usage:\t%s [-cdorstv] dirlist\n\n", progname); fprintf(stderr, "Flags:" "\t-c change absolute/messy links to relative\n" "\t-d delete dangling links\n" "\t-o warn about links across file systems\n" "\t-r recurse into subdirs\n" "\t-s shorten lengthy links (displayed in output only when -c not specified)\n" "\t-t show what would be done by -c\n" "\t-v verbose (show all symlinks)\n\n"); exit(1); } int main(int argc, char **argv) { static char path[PATH_MAX + 2], cwd[PATH_MAX + 2]; int dircount = 0; char c, *p; if ((progname = (char *) strrchr(*argv, '/')) == NULL) { progname = *argv; } else { progname++; } if (NULL == getcwd(cwd, PATH_MAX)) { fprintf(stderr, "getcwd() failed\n"); exit(1); } if (!*cwd || cwd[strlen(cwd) - 1] != '/') { strcat(cwd, "/"); } while (--argc) { p = *++argv; if (*p == '-') { if (*++p == '\0') { usage_error(); } while ((c = *p++)) { if (c == 'c') { fix_links = 1; } else if (c == 'd') { delete = 1; } else if (c == 'o') { single_fs = 0; } else if (c == 'r') { recurse = 1; } else if (c == 's') { shorten = 1; } else if (c == 't') { testing = 1; } else if (c == 'v') { verbose = 1; } else { usage_error(); } } } else { struct stat st; if (*p == '/') { *path = '\0'; } else { strcpy(path, cwd); } tidy_path(strcat(path, p)); if (lstat(path, &st) == -1) { perror(path); } else { dirwalk(path, strlen(path), st.st_dev); } ++dircount; } } if (dircount == 0) { usage_error(); } exit(0); }