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