Whamcloud - gitweb
libext2fs: simplify extent_fallocate()
[tools/e2fsprogs.git] / util / symlinks.c
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 occurrence 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 }