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