Whamcloud - gitweb
misc: fix broken libmagic interaction with plausibility check
[tools/e2fsprogs.git] / util / symlinks.c
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 }