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