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 -------------------------------------------------*/