3 * $Id: sw_links.c,v 1.1 1999/06/02 16:53:36 mdw Exp $
5 * Messing with symlink trees
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of sw-tools.
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.
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.
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.
29 /*----- Revision history --------------------------------------------------*
31 * $Log: sw_links.c,v $
32 * Revision 1.1 1999/06/02 16:53:36 mdw
37 /*----- Header files ------------------------------------------------------*/
46 #include <sys/types.h>
53 #include <mLib/alloc.h>
54 #include <mLib/dspool.h>
55 #include <mLib/dstr.h>
56 #include <mLib/report.h>
63 /*----- Data structures ---------------------------------------------------*/
72 /*----- Static variables --------------------------------------------------*/
76 static archcons
*alist
;
80 /*----- Main code ---------------------------------------------------------*/
84 * Arguments: @dstr *d@ = path string to canonify
88 * Use: Strips out all `dir/..' pairs in a path string. Also removes
89 * any `.' and empty components.
92 static void canon(dstr
*d
)
103 /* --- Initial stuff --- *
105 * If @path@ starts with `/' then initial `..' and `.' sequences can be
106 * discarded immediately. Remember this by remembering where the start of
111 p
= q
= path
= d
->buf
;
118 /* --- Now for the main job --- *
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@.
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.
138 /* --- Skip empty items --- */
143 /* --- Things with `.' in front of them --- */
148 else if (p
[1] == '/') {
151 } else if (p
[1] == '.' && (p
[2] == '/' || p
[2] == 0)) {
153 bp
= bp ? bp
- 1 : MAXBUF
- 1;
170 /* --- Normal things --- */
173 if (bp
>= MAXBUF
) bp
= 0;
177 while (*p
&& *p
!= '/')
184 /* --- Tidying up --- */
190 if (q
> path
&& q
[-1] == '/')
195 /* --- @linktree@ --- *
197 * Arguments: @int top@ = nonzero if this is toplevel
198 * @dstr *cont@ = continuation directory
200 * Returns: Zero if things are working well, nonzero otherwise.
202 * Use: Main recursive tree-linking algorithm. This makes extensive
203 * use of the dynamic strings @up@ and @down@ as stacks to
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
213 * See the `description of the algorithm' below to really
214 * understand what's going on here. It's not completely
218 static int linktree(int top
, dstr
*cont
)
229 /* --- Description of the algorithm ---
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.
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
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.
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
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.
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
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.
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.
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.
292 * That completes the algorithm.
296 /* --- Phase one: directory scan --- */
303 /* --- Open a directory stream --- */
305 if ((dp
= opendir(".")) == 0) {
306 moan("couldn't read directory `%s': %s", down
.buf
, strerror(errno
));
311 /* --- Read the entries out one by one --- */
314 while ((d
= readdir(dp
)) != 0) {
316 /* --- Skip `.' and `..' directories --- */
320 if (p
[0] == '.' && ((p
[1] == '.' && p
[2] == 0) || p
[1] == 0))
324 /* --- If this is toplevel, skip over the symlink trees --- */
328 for (a
= all
; a
; a
= a
->cdr
) {
329 if (strcmp(d
->d_name
, a
->car
->arch
) == 0)
334 /* --- Make a little structure with the entries in --- */
337 DPUTS(ds
, d
->d_name
);
350 /* -- Phase two: read attributes --- */
354 for (f
= fs
; f
; f
= f
->next
) {
356 /* --- Read the file information --- */
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
);
366 /* --- Handle symbolic links --- *
368 * I need to canonify relative symbolic links. (And there shouldn't be
369 * any absolute links in a source distribution!)
372 else if (S_ISLNK(f
->st
.st_mode
)) {
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
));
383 if (ds
->buf
[0] == '/')
398 /* --- Directories are easy: they get created the hard way --- */
400 else if (S_ISDIR(f
->st
.st_mode
))
403 /* --- Everything else is just a link --- */
417 /* --- Phase three: output links --- */
421 /* --- Step 1: change directory --- */
425 DPUTS(dd
, alist
->car
->arch
);
428 if (chdir(dd
->buf
+ 3)) {
429 die(1, "fatal error: couldn't change directory to `%s': %s",
430 dd
->buf
+ 3, strerror(errno
));
436 /* --- Step 2: populate with links --- */
441 for (f
= fs
; f
; f
= f
->next
) {
445 if (lstat(f
->name
->buf
, &st
) == 0 && S_ISLNK(st
.st_mode
))
446 unlink(f
->name
->buf
);
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
));
453 } else if (f
->name
) {
454 if (mkdir(f
->name
->buf
, f
->st
.st_mode
& 07777) &&
456 moan("couldn't create directory `%s%s/%s: %s",
457 e
->arch
, down
.buf
, f
->name
->buf
, strerror(errno
));
463 /* --- Step 3: move along --- */
471 DPUTS(dd
, a
->car
->arch
);
474 if (chdir(dd
->buf
)) {
475 die(1, "fatal error: couldn't change directory to `%s': %s",
476 dd
->buf
, strerror(errno
));
481 /* --- Interlude: filter out nondirectories from the file list --- *
483 * This is a memory-saving exercise, and it makes the subdirectory handling
491 if (f
->name
&& !f
->link
)
495 DSPUT(&pool
, f
->name
);
497 DSPUT(&pool
, f
->link
);
504 /* --- Interlude: early exit if no directories --- *
506 * Presumably, a call to @canon@ is cheaper than traversing too many
507 * directories in the kernel.
516 if (chdir(dd
->buf
)) {
517 die(1, "fatal error: couldn't change directory to `%s': %s",
518 dd
->buf
, strerror(errno
));
525 /* --- Phase four: process subdirectories --- */
528 size_t ulen
= up
.len
, dlen
= down
.len
;
530 /* --- Set current directory for first directory --- *
532 * Subsequent directories do the right thing with the @cont@ argument.
533 * Then just leave this one queued up for the next time around.
540 if (chdir(dd
->buf
)) {
541 die(1, "fatal error: couldn't change directory to `%s': %s",
542 dd
->buf
, strerror(errno
));
545 /* --- Now just process all the directories in turn --- */
550 /* --- Sort out the new `up' and `down' --- */
555 DPUTD(&down
, f
->name
);
558 /* --- Set up the continuation directory --- */
563 DPUTD(dd
, f
->next
->name
);
567 /* --- Clean up this node --- */
570 fent
*fnext
= f
->next
;
571 DSPUT(&pool
, f
->name
);
576 /* --- Go for it --- */
588 * Arguments: @const char *f@ = filename to snap
590 * Returns: Zero if ok, nonzero otherwise.
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.
598 static int snap(const char *f
)
607 /* --- Open the input file --- */
609 if ((ifd
= open(f
, O_RDONLY
)) < 0) {
610 moan("couldn't open `%s' for reading: %s", f
, strerror(errno
));
614 if (fstat(ifd
, &st
)) {
615 moan("couldn't read information about `%s': %s", f
, strerror(errno
));
619 /* --- Count the architectures --- */
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));
625 /* --- Make the directories needed, remove the old files, and so on --- */
630 char *p
= xstrdup(f
);
633 for (i
= 0; i
< narch
; 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);
644 for (i
= 0; i
< narch
; i
++) {
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
));
656 /* --- Main data copy loop --- */
664 n
= read(ifd
, buf
, sizeof(buf
));
666 moan("error reading `%s': %s", f
, strerror(errno
));
668 for (i
= 0; i
< narch
; i
++) {
677 for (i
= 0; i
< narch
; i
++) {
680 if (write(fd
[i
], buf
, n
) < 0) {
681 moan("error writing `%s', %s", d
[i
].buf
, strerror(errno
));
691 /* --- Set the state on the finished files --- */
697 u
.actime
= st
.st_atime
;
698 u
.modtime
= st
.st_mtime
;
700 for (i
= 0; i
< narch
; i
++) {
703 chmod(d
[i
].buf
, st
.st_mode
& 07777);
715 /*----- Subcommands -------------------------------------------------------*/
717 /* --- @sw_link@ --- */
719 int sw_link(int argc
, char *argv
[])
725 die(1, "Usage: linktree");
727 /* --- Initialize the dynamic strings --- */
730 dstr_puts(&up
, "../");
733 dspool_create(&pool
, 32);
735 /* --- Set up the architecture lists --- */
737 if (swinfo_fetch(&sw
)) {
738 die(1, "couldn't read build status: %s (try running setup)",
742 all
= arch_readtab();
743 alist
= swbuild_archlist(&sw
);
746 moan("All desired architectures already built!");
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
));
765 rc
= linktree(1, &d
);
769 /* --- Clean up the mess --- */
771 dspool_destroy(&pool
);
775 /* --- @sw_snap@ --- */
777 int sw_snap(int argc
, char *argv
[])
784 die(1, "Usage: snaplink FILE...");
786 /* --- Set up the architecture lists --- */
788 if (swinfo_fetch(&sw
)) {
789 die(1, "couldn't read build status: %s (try running setup)",
793 alist
= swbuild_archlist(&sw
);
796 moan("All desired architectures already built!");
800 for (i
= 1; i
< argc
; i
++) {
808 /*----- That's all, folks -------------------------------------------------*/