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