Home | History | Annotate | Download | only in util
      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