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 
substr(char * s,char * old,char * new)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 
tidy_path(char * path)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 
shorten_path(char * path,char * abspath)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 
fix_symlink(char * path,dev_t my_dev)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 
dirwalk(char * path,int pathlen,dev_t dev)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 
usage_error(void)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 
main(int argc,char ** argv)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