chiark / gitweb /
usage: Print metavariables in SHOUTY letters.
[sw-tools] / src / sw_links.c
1 /* -*-c-*-
2  *
3  * $Id: sw_links.c,v 1.2 2004/04/08 01:52:19 mdw Exp $
4  *
5  * Messing with symlink trees
6  *
7  * (c) 1999 EBI
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of sw-tools.
13  *
14  * sw-tools is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * sw-tools is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with sw-tools; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include <errno.h>
32 #include <limits.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <time.h>
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <dirent.h>
41 #include <unistd.h>
42 #include <utime.h>
43 #include <fcntl.h>
44
45 #include <mLib/alloc.h>
46 #include <mLib/dspool.h>
47 #include <mLib/dstr.h>
48 #include <mLib/report.h>
49 #include <mLib/sub.h>
50
51 #include "sw_arch.h"
52 #include "sw_build.h"
53 #include "sw_links.h"
54
55 /*----- Data structures ---------------------------------------------------*/
56
57 typedef struct fent {
58   struct fent *next;
59   dstr *name;
60   dstr *link;
61   struct stat st;
62 } fent;
63
64 /*----- Static variables --------------------------------------------------*/
65
66 static dstr down;
67 static dstr up;
68 static archcons *alist;
69 static archcons *all;
70 static dspool pool;
71
72 /*----- Main code ---------------------------------------------------------*/
73
74 /* --- @canon@ --- *
75  *
76  * Arguments:   @dstr *d@ = path string to canonify
77  *
78  * Returns:     ---
79  *
80  * Use:         Strips out all `dir/..' pairs in a path string.  Also removes
81  *              any `.' and empty components.
82  */
83
84 static void canon(dstr *d)
85 {
86 #define MAXBUF 16
87   char *b[MAXBUF];
88   int bp;
89   int v, n;
90   int retry;
91   char *path;
92   char *p, *q;
93   int r;
94
95   /* --- Initial stuff --- *
96    *
97    * If @path@ starts with `/' then initial `..' and `.' sequences can be
98    * discarded immediately.  Remember this by remembering where the start of
99    * the string is.
100    */
101
102   DPUTZ(d);
103   p = q = path = d->buf;
104   if (*p == '/') {
105     p++; q++; path++;
106     r = 1;
107   } else
108     r = 0;
109
110   /* --- Now for the main job --- *
111    *
112    * Scan each path component.  If it's a normal name, store @q@ in my
113    * circular buffer, and copy its text from @p@ to @q@.  If it's blank,
114    * or `.' then skip @p@ past it.  If it's `..', and there's an entry in my
115    * buffer, then reset @q@ back to that position and skip @p@ on past;
116    * otherwise, copy it to @q@.
117    *
118    * Complications arise when the buffer gets full.  Old entries are
119    * discarded off the bottom.  If it turns out they were useful (because
120    * @n@ is zero, but @v@ isn't) then @retry@ is set and we go round again
121    * when we're finished.
122    */
123
124 again:
125   bp = n = v = 0;
126   retry = 0;
127
128   while (*p) {
129
130     /* --- Skip empty items --- */
131
132     while (*p == '/')
133       p++;
134
135     /* --- Things with `.' in front of them --- */
136
137     if (*p == '.') {
138       if (p[1] == 0)
139         break;
140       else if (p[1] == '/') {
141         p += 2;
142         continue;
143       } else if (p[1] == '.' && (p[2] == '/' || p[2] == 0)) {
144         if (n) {
145           bp = bp ? bp - 1 : MAXBUF - 1;
146           q = b[bp];
147           n--; v--;
148           p += 2;
149           continue;
150         } else {
151           if (v)
152             retry = 1;
153           else if (r) {
154             p += 2;
155             continue;
156           }
157           goto out;
158         }
159       }
160     }
161           
162     /* --- Normal things --- */
163
164     b[bp++] = q;
165     if (bp >= MAXBUF) bp = 0;
166     v++;
167     if (n < MAXBUF) n++;
168   out:
169     while (*p && *p != '/')
170       *q++ = *p++;
171     if (*p == '/')
172       *q++ = *p++;
173   }
174   *q = 0;
175
176   /* --- Tidying up --- */
177
178   if (retry) {
179     p = q = path;
180     goto again;
181   }
182   if (q > path && q[-1] == '/')
183     q[-1] = 0;
184   d->len = q - d->buf;
185 }
186
187 /* --- @linktree@ --- *
188  *
189  * Arguments:   @int top@ = nonzero if this is toplevel
190  *              @dstr *cont@ = continuation directory
191  *
192  * Returns:     Zero if things are working well, nonzero otherwise.
193  *
194  * Use:         Main recursive tree-linking algorithm.  This makes extensive
195  *              use of the dynamic strings @up@ and @down@ as stacks to
196  *              maintain state.
197  *
198  *              On entry, @down@ contains the name of the path to scan,
199  *              relative to the common root; @up@ is the inverse path, from
200  *              the directory @down@ back up to the root.  As a special
201  *              wrinkle, @up@ has an additional `../' on the front.  The
202  *              current directory is @down@.  On exit, the current directory
203  *              is set to @cont@.
204  *
205  *              See the `description of the algorithm' below to really
206  *              understand what's going on here.  It's not completely
207  *              trivial.
208  */
209
210 static int linktree(int top, dstr *cont)
211 {
212   int rc = 0;
213   fent *fs;
214   dstr *dd;
215
216   if (!alist)
217     return (0);
218
219   DSGET(&pool, dd);
220
221   /* --- Description of the algorithm ---
222    *
223    * This is the sort of thing which is easy to do badly and hard to do
224    * well.  The current algorithm seeks to minimize the amount of searching
225    * that the operating system has to do around the filesystem, by changing
226    * the current directory fairly often.  But it also tries to avoid using
227    * too much memory, and never visits the same directory twice.
228    *
229    * I start off in the `common root'.  This is the directory that the user
230    * actually wanted to make link trees of, and is the parent of all the link
231    * trees.  The algorithm keeps track of where it's meant to be using two
232    * (static) variables, @down@ and @up@.  The @down@ variable tracks the
233    * current directory relative to the common root, and the @up@ variable
234    * contains enough `../'s to get back to the common root from the current
235    * directory, and one more (for getting back into the main tree from one of
236    * the architecture link trees, which has an extra level of depth).  When a
237    * recursion stage is entered, the current directory is already set
238    * correctly.
239    *
240    * At any stage in the recursion, there is a `continuation' directory.
241    * This is where my caller wants you to go when I've finished my job.  If I
242    * have no subdirectories to cope with, then I just change into the
243    * continuation when I've finished making all my links.  The trick with
244    * continations really works with subdirectories.  I change into the first
245    * subdirectory in my list myself, passing it a continuation for the next
246    * subdirectory in sequence.  The last subdirectory gets given a modified
247    * version of my own contination which keeps my caller happy without me
248    * actually having to do anything.
249    *
250    * The actual work is done by a postorder traversal.  A directory is
251    * processed in four phases, with an interlude between phases three and
252    * four.
253    *
254    *   * Phase one scans the current directory, and stores the names of
255    *     everything in a list.  Uninteresting things like `.' and `..', and
256    *     the architecture trees at toplevel, are filtered out at this stage.
257    *     When this scan is complete, I no longer need a file descriptor open
258    *     on the directory, and I can close it.
259    *
260    *   * Phase two examines each item scanned in phase one, and determines
261    *     how to deal with it.  Directory objects turn into real hard
262    *     directories; symlinks turn into adjusted symlinks to the same
263    *     destination; and files turn into relative symlinks of the right
264    *     kind.
265    *
266    *   * Phase three moves into the corresponding link directory for each
267    *     architecture, and makes the links and directories decided upon in
268    *     phase two.  When phase three is complete, the current directory is
269    *     still the link directory for the last architecture.
270    *
271    *   * The interlude tidies up some internal structures a little, and
272    *     handles early exit.  Firstly, the list of objects is filtered, and
273    *     everything which isn't a directory is removed.  Secondly, if the
274    *     list is now empty, the algorithm does its `early exit': it works out
275    *     how to get to the continuation directory from where it is now, does
276    *     that, and then returns.
277    *
278    *   * Phase four does the recursion step.  There is at least one
279    *     subdirectory to deal with.  Change out of the link tree, and into
280    *     the first subdirectory of my main directory.  Now, for each
281    *     subdirectory, recursively build trees, setting @down@, @up@ and the
282    *     continuation according to the description above.
283    *
284    * That completes the algorithm.
285    */
286
287   {
288     /* --- Phase one: directory scan --- */
289
290     DIR *dp;
291     struct dirent *d;
292     fent **ff, *f;
293     dstr *ds;
294
295     /* --- Open a directory stream --- */
296
297     if ((dp = opendir(".")) == 0) {
298       moan("couldn't read directory `%s': %s", down.buf, strerror(errno));
299       chdir(cont->buf);
300       return (-1);
301     }
302
303     /* --- Read the entries out one by one --- */
304
305     ff = &fs;
306     while ((d = readdir(dp)) != 0) {
307
308       /* --- Skip `.' and `..' directories --- */
309
310       {
311         char *p = d->d_name;
312         if (p[0] == '.' && ((p[1] == '.' && p[2] == 0) || p[1] == 0))
313           goto skip;
314       }
315
316       /* --- If this is toplevel, skip over the symlink trees --- */
317
318       if (top) {
319         archcons *a;
320         for (a = all; a; a = a->cdr) {
321           if (strcmp(d->d_name, a->car->arch) == 0)
322             goto skip;
323         }
324       }
325
326       /* --- Make a little structure with the entries in --- */
327
328       DSGET(&pool, ds);
329       DPUTS(ds, d->d_name);
330       f = CREATE(fent);
331       f->name = ds;
332       *ff = f;
333       ff = &f->next;
334     skip:;
335     }
336
337     closedir(dp);
338     *ff = 0;
339   }
340
341   {
342     /* -- Phase two: read attributes --- */
343
344     fent *f;
345
346     for (f = fs; f; f = f->next) {
347
348       /* --- Read the file information --- */
349
350       if (lstat(f->name->buf, &f->st)) {
351         moan("couldn't stat file `%s%s': %s",
352              down.buf, f->name->buf, strerror(errno));
353         DSPUT(&pool, f->name);
354         f->name = 0;
355         rc = -1;
356       }
357
358       /* --- Handle symbolic links --- *
359        *
360        * I need to canonify relative symbolic links.  (And there shouldn't be
361        * any absolute links in a source distribution!)
362        */
363
364       else if (S_ISLNK(f->st.st_mode)) {
365         dstr *ds;
366         int i;
367         DSGET(&pool, ds);
368         DENSURE(ds, f->st.st_size + 1);
369         if ((i = readlink(f->name->buf, ds->buf, ds->sz)) < 0) {
370           moan("couldn't read symbolic link `%s%s': %s",
371                down.buf, f->name->buf, strerror(errno));
372         } else {
373           ds->buf[i] = 0;
374           ds->len = i;
375           if (ds->buf[0] == '/')
376             f->link = ds;
377           else {
378             dstr *d;
379             DSGET(&pool, d);
380             f->link = d;
381             DPUTD(d, &up);
382             DPUTD(d, &down);
383             DPUTD(d, ds);
384             canon(d);
385             DSPUT(&pool, ds);
386           }
387         }
388       }
389
390       /* --- Directories are easy: they get created the hard way --- */
391
392       else if (S_ISDIR(f->st.st_mode))
393         f->link = 0;
394
395       /* --- Everything else is just a link --- */
396
397       else {
398         dstr *d;
399         DSGET(&pool, d);
400         f->link = d;
401         DPUTD(d, &up);
402         DPUTD(d, &down);
403         DPUTD(d, f->name);
404       }
405     }
406   }
407
408   {
409     /* --- Phase three: output links --- */
410
411     archcons *a;
412
413     /* --- Step 1: change directory --- */
414
415     dd->len = 0;
416     DPUTD(dd, &up);
417     DPUTS(dd, alist->car->arch);
418     DPUTC(dd, '/');
419     DPUTD(dd, &down);
420     if (chdir(dd->buf + 3)) {
421       die(1, "fatal error: couldn't change directory to `%s': %s",
422           dd->buf + 3, strerror(errno));
423     }
424
425     a = alist;
426     for (;;) {
427
428       /* --- Step 2: populate with links --- */
429
430       archent *e = a->car;
431       fent *f;
432
433       for (f = fs; f; f = f->next) {
434         if (f->link) {
435           {
436             struct stat st;
437             if (lstat(f->name->buf, &st) == 0 && S_ISLNK(st.st_mode))
438               unlink(f->name->buf);
439           }
440           if (symlink(f->link->buf, f->name->buf)) {
441             moan("couldn't create link `%s%s/%s': %s",
442                  e->arch, down.buf, f->name->buf, strerror(errno));
443             rc = -1;
444           }
445         } else if (f->name) {
446           if (mkdir(f->name->buf, f->st.st_mode & 07777) &&
447               errno != EEXIST) {
448             moan("couldn't create directory `%s%s/%s: %s",
449                  e->arch, down.buf, f->name->buf, strerror(errno));
450             rc = -1;
451           }
452         }
453       }
454
455       /* --- Step 3: move along --- */
456
457       a = a->cdr;
458       if (!a)
459         break;
460
461       dd->len = 0;
462       DPUTD(dd, &up);
463       DPUTS(dd, a->car->arch);
464       DPUTC(dd, '/');
465       DPUTD(dd, &down);
466       if (chdir(dd->buf)) {
467         die(1, "fatal error: couldn't change directory to `%s': %s",
468             dd->buf, strerror(errno));
469       }
470     }
471   }
472
473   /* --- Interlude: filter out nondirectories from the file list --- *
474    *
475    * This is a memory-saving exercise, and it makes the subdirectory handling
476    * simpler.
477    */
478
479   {
480     fent **ff = &fs;
481     while (*ff) {
482       fent *f = *ff;
483       if (f->name && !f->link)
484         ff = &f->next;
485       else {
486         if (f->name)
487           DSPUT(&pool, f->name);
488         if (f->link)
489           DSPUT(&pool, f->link);
490         *ff = f->next;
491         DESTROY(f);
492       }
493     }
494   }
495
496   /* --- Interlude: early exit if no directories --- *
497    *
498    * Presumably, a call to @canon@ is cheaper than traversing too many
499    * directories in the kernel.
500    */
501
502   if (!fs) {
503     dd->len = 0;
504     DPUTD(dd, &up);
505     DPUTD(dd, &down);
506     DPUTD(dd, cont);
507     canon(dd);
508     if (chdir(dd->buf)) {
509       die(1, "fatal error: couldn't change directory to `%s': %s",
510           dd->buf, strerror(errno));
511     }
512     DSPUT(&pool, dd);
513     return (rc);
514   }
515
516   {
517     /* --- Phase four: process subdirectories --- */
518
519     fent *f;
520     size_t ulen = up.len, dlen = down.len;
521
522     /* --- Set current directory for first directory --- *
523      *
524      * Subsequent directories do the right thing with the @cont@ argument.
525      * Then just leave this one queued up for the next time around.
526      */
527
528     dd->len = 0;
529     DPUTD(dd, &up);
530     DPUTD(dd, &down);
531     DPUTD(dd, fs->name);
532     if (chdir(dd->buf)) {
533       die(1, "fatal error: couldn't change directory to `%s': %s",
534           dd->buf, strerror(errno));
535     }
536
537     /* --- Now just process all the directories in turn --- */
538
539     f = fs;
540     while (f) {
541
542       /* --- Sort out the new `up' and `down' --- */
543
544       up.len = ulen;
545       down.len = dlen;
546       DPUTS(&up, "../");
547       DPUTD(&down, f->name);
548       DPUTS(&down, "/");
549
550       /* --- Set up the continuation directory --- */
551
552       dd->len = 0;
553       DPUTS(dd, "../");
554       if (f->next)
555         DPUTD(dd, f->next->name);
556       else
557         DPUTD(dd, cont);
558
559       /* --- Clean up this node --- */
560
561       {
562         fent *fnext = f->next;
563         DSPUT(&pool, f->name);
564         DESTROY(f);
565         f = fnext;
566       }
567
568       /* --- Go for it --- */
569
570       linktree(0, dd);
571     }
572   }
573
574   DSPUT(&pool, dd);
575   return (rc);
576 }
577
578 /* --- @snap@ --- *
579  *
580  * Arguments:   @const char *f@ = filename to snap
581  *
582  * Returns:     Zero if ok, nonzero otherwise.
583  *
584  * Use:         Snaps a symlink in one of the symlink trees into a real file.
585  *              Also (by design) happens to work even if there wasn't a
586  *              symlink there to begin with, in which case any necessary
587  *              directories are created beforehand.
588  */
589
590 static int snap(const char *f)
591 {
592   int narch;
593   int *fd;
594   int ifd;
595   struct stat st;
596   dstr *d;
597   int rc = 0;
598
599   /* --- Open the input file --- */
600
601   if ((ifd = open(f, O_RDONLY)) < 0) {
602     moan("couldn't open `%s' for reading: %s", f, strerror(errno));
603     return (-1);
604   }
605
606   if (fstat(ifd, &st)) {
607     moan("couldn't read information about `%s': %s", f, strerror(errno));
608     return (-1);
609   }  
610
611   /* --- Count the architectures --- */
612
613   { archcons *a; for (a = alist, narch = 0; a; a = a->cdr, narch++) ; }
614   d = xmalloc(narch * sizeof(dstr));
615   fd = xmalloc(narch * sizeof(int));
616
617   /* --- Make the directories needed, remove the old files, and so on --- */
618
619   {
620     int i;
621     archcons *a;
622     char *p = xstrdup(f);
623     char *q;
624
625     for (i = 0; i < narch; i++)
626       DCREATE(&d[i]);
627     for (i = 0, a = alist; a; i++, a = a->cdr)
628       DPUTS(&d[i], a->car->arch);
629     for (q = strtok(p, "/"); q; q = strtok(0, "/")) {
630       for (i = 0; i < narch; i++) {
631         mkdir(d[i].buf, 0775);
632         DPUTC(&d[i], '/');
633         DPUTS(&d[i], q);
634       }
635     }
636     for (i = 0; i < narch; i++) {
637       unlink(d[i].buf);
638       if ((fd[i] = open(d[i].buf, O_WRONLY | O_TRUNC | O_CREAT,
639                         st.st_mode & 07777)) < 0) {
640         moan("couldn't open `%s' for writing: %s",
641              d[i].buf, strerror(errno));
642         rc = -1;
643       }
644     }
645     free(p);
646   }
647
648   /* --- Main data copy loop --- */
649
650   {
651     int i;
652     char buf[BUFSIZ];
653     ssize_t n;
654
655     for (;;) {
656       n = read(ifd, buf, sizeof(buf));
657       if (n < 0) {
658         moan("error reading `%s': %s", f, strerror(errno));
659         rc = -1;
660         for (i = 0; i < narch; i++) {
661           close(fd[i]);
662           fd[i] = -1;
663           unlink(d[i].buf);
664         }
665         break;
666       }
667       if (!n)
668         break;
669       for (i = 0; i < narch; i++) {
670         if (fd[i] < 0)
671           continue;
672         if (write(fd[i], buf, n) < 0) {
673           moan("error writing `%s', %s", d[i].buf, strerror(errno));
674           close(fd[i]);
675           fd[i] = -1;
676           unlink(d[i].buf);
677           rc = -1;
678         }
679       }
680     }
681   }
682
683   /* --- Set the state on the finished files --- */
684
685   {
686     int i;
687     struct utimbuf u;
688
689     u.actime = st.st_atime;
690     u.modtime = st.st_mtime;
691
692     for (i = 0; i < narch; i++) {
693       if (fd[i] >= 0) {
694         close(fd[i]);
695         chmod(d[i].buf, st.st_mode & 07777);
696         utime(d[i].buf, &u);
697       }
698       DDESTROY(&d[i]);
699     }
700   }
701
702   free(d);
703   free(fd);
704   return (rc);
705 }
706
707 /*----- Subcommands -------------------------------------------------------*/
708
709 /* --- @sw_link@ --- */
710
711 int sw_link(int argc, char *argv[])
712 {
713   int rc = 0;
714   swinfo sw;
715
716   if (argc != 1)
717     die(1, "Usage: linktree");
718
719   /* --- Initialize the dynamic strings --- */
720
721   dstr_create(&up);
722   dstr_puts(&up, "../");
723   dstr_create(&down);
724   dstr_putz(&down);
725   dspool_create(&pool, 32);
726
727   /* --- Set up the architecture lists --- */
728
729   if (swinfo_fetch(&sw)) {
730     die(1, "couldn't read build status: %s (try running setup)",
731         strerror(errno));
732   }
733   swinfo_sanity(&sw);
734   all = arch_readtab();
735   alist = swbuild_archlist(&sw);
736
737   if (!alist) {
738     moan("All desired architectures already built!");
739     return (0);
740   }
741
742   {
743     archcons *a;
744     for (a = alist; a; a = a->cdr) {
745       if (mkdir(a->car->arch, 0775) && errno != EEXIST) {
746         moan("couldn't create architecture tree `%s': %s",
747              a->car->arch, strerror(errno));
748         rc = -1;
749       }
750     }
751   }
752
753   /* --- Go --- */
754
755   if (rc == 0) {
756     dstr d = DSTR_INIT;
757     rc = linktree(1, &d);
758     DDESTROY(&d);
759   }
760
761   /* --- Clean up the mess --- */
762
763   dspool_destroy(&pool);
764   return (!!rc);
765 }
766
767 /* --- @sw_snap@ --- */
768
769 int sw_snap(int argc, char *argv[])
770 {
771   int rc = 0;
772   swinfo sw;
773   int i;
774
775   if (argc < 2)
776     die(1, "Usage: snaplink FILE...");
777
778   /* --- Set up the architecture lists --- */
779
780   if (swinfo_fetch(&sw)) {
781     die(1, "couldn't read build status: %s (try running setup)",
782         strerror(errno));
783   }
784   swinfo_sanity(&sw);
785   alist = swbuild_archlist(&sw);
786
787   if (!alist) {
788     moan("All desired architectures already built!");
789     return (0);
790   }
791
792   for (i = 1; i < argc; i++) {
793     if (snap(argv[i]))
794       rc = 1;
795   }
796
797   return (rc);
798 }
799
800 /*----- That's all, folks -------------------------------------------------*/