Build separately from mLib.
[sw-tools] / src / sw_links.c
CommitLineData
3315e8b3 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
65typedef struct fent {
66 struct fent *next;
67 dstr *name;
68 dstr *link;
69 struct stat st;
70} fent;
71
72/*----- Static variables --------------------------------------------------*/
73
74static dstr down;
75static dstr up;
76static archcons *alist;
77static archcons *all;
78static 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
92static 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
132again:
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
218static 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
598static 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
719int 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
777int 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 -------------------------------------------------*/