[alt.sources] lktree: symbolic link tree builder that understands RCS & SCCS

mark@zok.UUCP (Mark W. Snitily) (11/01/90)

Note:  I'm posting this for a friend that doesn't have regular net access.
Cecil McGregor is the author.

As the subject line says, this is a program that builds a symbolic
link tree.  It recognizes RCS & SCCS subdirectories and symlinks to
the directory instead of all of the individual RCS or SCCS files.
The program is in use on sun[34]'s running Sun OS 4.0.3.

Questions can be emailed to me and I'll forward them on to Cecil.

-- Mark

Mark W. Snitily                 Consulting Services:
894 Brookgrove Lane             Graphics, Operating Systems, Compilers
Cupertino, CA 95014             (408) 252-0456
mark@zok.uucp                   West Coast UUCP X11 archive site

If your mailer doesn't like the .uucp domain, these also work:
...!{mips,sgi}!zok!mark, mark%zok@mips.com, mark%zok@sgi.com

---<cut here>------------------------------------------------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  lktree.1 lktree.c
# Wrapped by mark@zok on Wed Oct 31 23:51:41 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'lktree.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lktree.1'\"
else
echo shar: Extracting \"'lktree.1'\" \(7019 characters\)
sed "s/^X//" >'lktree.1' <<'END_OF_FILE'
X...
X... $Header: /wrld/6e50/cecil/tools/RCS/lktree.1,v 1.3 89/08/04 17:03:23 cecil Exp $
X... 
X... $Log:	lktree.1,v $
XRevision 1.3  89/08/04  17:03:23  cecil
XAdded comments for comparing lktree to 'cp -r' command.
X
XRevision 1.2  89/08/04  09:53:11  cecil
XAdded information for multiple runs of lktree on the same
Xdevelopment tree.
XAdded "scary messages" to BUGS
XAdded Author section since people did not believe Cecil
Xprogrammed this!
X
XRevision 1.1  89/08/03  14:17:12  cecil
XInitial revision
X
X...
X.TH LKTREE 1C "4 Aug 89"
X.SH NAME
X.sp .5
Xlktree \- link tree builder
X.SH SYNOPSIS
X.sp .5
X.B lktree
X.B [
X.I \ root_tree 
X.B \-I
X.I incl_file 
X.B \ \-X
X.I excl_file
X.I root_tree 
X.B ]
X.SH DESCRIPTION
X.IX  "lktree command"  ""  "\fLlktree\fP \(em link tree builder"
X.I lktree
Xgenerates a topologically equivalent tree at the current location
Xthat corresponds to the directory structure at
X.I root_tree
XThe 
X.I incl_file 
Xand 
X.I excl_file 
Xare file matching arguements
Xthat are used to include and exclude files from the tree
Xlinking operations.
X.LP
X.I lktree
Xis similar to
X.I cp \-r
Xin that it recursively copies directory structures.  The difference
Xis that
X.I lktree
Xis selective in the files in includes and excludes.
X.I lktree
Xalso builds links rather than performing file copies.
X.I lktree
Xknows that
X.B RCS
Xwill be used to maintain a programming development system for use
Xby multiple programmers.  Thus all
X.B RCS
Xdirectories are linked to the original development tree while
Xall other directories are actually created in the programmers
Xarea.  This allows for natural use of
X.B RCS
Xcommands that are (hopefully) transparent to development.
X.LP
XIf a
X.I root_tree
Xdoes not exist, the environment variable
X.BI "ROOT_TREE"
Xis used to anchor the topological structure.
X.BI "ROOT_TREE"
Xis used to provide easy transition to multiple projects
Xin a portable, configurable manner.
X.LP
XThe
X.I root_tree
Xshould be a fully qualified directory name for a currently
Xexisting directory.  It is an error to do otherwise.
X.IR incl_file
Xand
X.IR excl_file
Xdo not have to be present.  If these two files are
Xmissing then an open attempt is made on 
X.BI $ROOT_TREE
X/incl_file
Xand 
X.BI $ROOT_TREE
X/excl_file.  This allows defaulting
Xof the non-essential files in a project-wide manner.
X
X.I lktree
Xis designed to be used with no parameters; it is the
Xresponsibility of the system adminstrator to configure
Xprogrammer .cshrc files to contain the necessary
Xenvironment.  Programmers working on multiple
Xprojects should access the 
X.BI ROOT_TREE
X/.cshrc for particular tree needed.  The local project 
X.BI ROOT_TREE
X/.cshrc
Xcontains the environmental setting to access everything
Xabout that project.
X.LP
X.B RCS
Xdirectories are a specail case.  The intent of this utility
Xis to provide a convenient method for programmers to 
Xhave separate development trees and to use 
X.B RCS
Xas a backup feature and configuration management utility.
X.B RCS
Xis also used to insure that only one programmer at a time has
Xa file checked out.
X.LP
XWith the linked
X.B RCS
Xdirectory the programmer has easy access to the
X.B RCS
X.I co
Xand
X.I ci
Xutilities.
XAfter building the link tree, the programmer will notice that
Xall files are linked to
X.B $ROOT_TREE
Xfiles, while the programmer has true directories.  Furthermore,
XRCS exists as a link, not as a directory.  This link allows
Xmultiple programmers to check out RCS files in an orderly manner.
XAfter developing and adding modules to the root tree
Xthe new modules must be distributed to the development team.
XThe is done by simply running
X.I lktree
Xonce again in each developer's base development directory.
XThis will "refresh" the developer's tree to match the
X.B $ROOT_TREE
Xfiles. Specifically files that are locked by developers
Xwill not be disturbed.
X.LP
XProgrammers will note that object files are neither copied
Xnor symbolically linked.  This allows individual programmers
Xto have their own copy of an object file. Each programmer
Xmust "make" his own tree in order to obtain executables.
X.LP
X.I incl_file
Xis normally in the 
X.BI ROOT_TREE
Xdirectory and contains an 
X.I fgrep 
Xlist of files to be
Xincluded in the linking operation. An example that selects
Xall c, h and makefiles is:
X.RS
X.nf
X.cc @
X.c
Xmake
X.h
X@cc .
X.fi
X.RE
XThe leading period insures that files the "dot c and h" files
Xare the files selected, not files that simply contain a c or h.
X
X.I excl_file
Xis normally in the 
X.BI ROOT_TREE 
Xdirectory and contains a
Xsimilar fgrep file of files to be excluded to the link
Xtree being built.  The 
X.I excl_file 
Xis applied to the file candidates after 
X.I incl_file
Xselects a list of possible candidates to linking.
XThe
X.I excl_file
Xis meant to exclude directories such as 
X.B SCCS
Xfrom linking.  An example that excludes all 
X.B SCCS 
Xdirectories,
Xall 
X.B BAK 
Xfiles (as output from
X.I indent
X), and miscellaneous non-essential files (generated by cross compilers):
X.RS
X.nf
XSCCS
XBAK
Xlst
Xobj
Xmrg
Xxrf
Xcrf
X.fi
X.RE
X.SH FILES
XDefault files are assumed in the environment root tree
X.BI $ROOT_TREE.
X.I incl_file
Xand 
X.I excl_file
Xare defined by the configuration manager.
X.BI $ROOT_TREE
Xitself is an environmental variable that is a directory to the
Xbaseline of source code to be used by multiple
Xprogrammers in software development.  It is not necessarily
Xrestricted to pure code, but may also include documentation.
X.SH AUTHOR
XCecil McGregor
X.SH SEE ALSO
Xdco(1c), dci(1c) and the configuration manager's procedures.
X.SH BUGS
X\fIlktree\fP is meant to create link trees on a single file
Xserver, attempting
X.I lktree
Xacross file servers is untested.
X.LP
X.I lktree
Xcopies to the current directory, programmers should create
Xtheir root directory, descend into it and 
X.I then
Xperform
X.I lktree
Xoperations.
X.LP
X.I lktree
Xwill work correctly on an already existing link tree.
XThis is useful when new modules are added to the currently
Xexisting 
X.BI ROOT_TREE 
Xand developers must work with them.
X.LP
X.I lktree
Xrequires permission and access bits to be set properly on
Xthe 
X.BI ROOT_TREE 
Xso programmers can read files.
X.LP
X.I lktree
Xassumes that the system adminstrator performs backups on
Xprogrammer link trees as well as 
X.BI ROOT_TREE.
X.LP
XWhen rerunning
X.I lktree
Xin an already built tree many scary messages are generated.
XThese messages should be suppressed and only the successful
Xlinks reported.  This would give the programmer confidence that
Xthe system is operating properly as well as inform him of
Xthe new links that were performed.
X.LP
XOrphan links might be created when running
X.I lktree
Xmultiple times on the same root tree.  This needs a utility
Xto inform the development programmer that there is an orphan.
XAn orphan link is a file that originally existed in the
Xroot development tree, but was either renamed or removed.
XThe development tree retains the symbolic link to the original
Xfile but an attempted access to it will result in an error
Xmessage.  It should be the responsibility of the configuration
Xmanager to resolve these problems; the orphan-link utility
Xis simply part of his tool kit.
X.ex
END_OF_FILE
if test 7019 -ne `wc -c <'lktree.1'`; then
    echo shar: \"'lktree.1'\" unpacked with wrong size!
fi
# end of 'lktree.1'
fi
if test -f 'lktree.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lktree.c'\"
else
echo shar: Extracting \"'lktree.c'\" \(8287 characters\)
sed "s/^X//" >'lktree.c' <<'END_OF_FILE'
Xstatic char RcsId[] = "$Header: /wrld/6e50/cecil/tools/RCS/lktree.c,v 1.2 89/08/04 10:40:32 cecil Exp Locker: cecil $";
X/*
X	$Log:	lktree.c,v $
X * Revision 1.2  89/08/04  10:40:32  cecil
X * adding lktree to RCS control.
X * 
X * Revision 1.1  89/08/04  08:50:07  cecil
X * Initial revision
X * 
X*/
X
X#include <stdio.h>
X#include <ftw.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <string.h>
X
XFILE *fp;
X
Xint debug = 0;			/* TRUE to debug */
X#define DEBUG(x) {if(debug)fprintf(stderr, "%s:%d:%s\n", __FILE__,__LINE__,x);}
X
Xint dir_ignore = 0;		/* option -d == ignore directories */
Xint link_ignore = 0;		/* option -l == ignore links */
X
Xchar *head;			/* the requested root head */
Xchar head_buf[512];		/* work buffer for environmental variable */
X
XFILE *out_fp;			/* temp file for fgrep's */
Xchar *temp_fn;			/* return from mktemp() */
X
XFILE *incl_fp = 0, *excl_fp = 0;	/* include and exclude file ptrs */
Xchar *incl_fn, *excl_fn;		/* include and exclude file names */
X
Xchar *rcs_root_fn;		/* name of root directory to start link */
X
X/* strdup - duplicate a string */
Xchar *strdup(s)
Xchar *s;
X{
X	char *cp;
X
X	cp = (char *)malloc(strlen(s)+1);
X	strcpy(cp, s);
X	return cp;
X}
X
X/*==================================================================*/
X/* strsearch - search string for sub_string, return ptr to start of */
X/* sub_string within string, NULL if not within.                    */
X/*==================================================================*/
X
Xchar *strsearch(string, sub_string)
Xchar *string, *sub_string;
X{
X	for(;string = (char *)index(string, *sub_string);++string){
X	    /* found first char of substring in string */
X	    if(strcmp(string, sub_string) == 0)
X		return string;
X	}
X	/* substring is not within string, return NULL */
X	return (char *)0;
X}
X
Xint print_file(fn, sbuf, ftype)
Xchar *fn;			/* name of file */
Xstruct stat *sbuf;
Xint ftype;
X{
X    register char *name;
X    char buf[BUFSIZ];
X    char *temp;
X    int status;
X    
X    DEBUG(fn);
X    /* If this file is a symbolic link, then absolutely */
X    /* ignore this reference!                           */
X    /* This call succeeds only if fn IS a link.         */
X    if((status = readlink(fn, buf, BUFSIZ)) != -1){
X	DEBUG("ignoring symbolic link file");
X	return 0;
X    }
X
X    /* Never descend into RCS directories, as the intent */
X    /* is to link an RCS directory instead ths individual*/
X    /* files in the directory.                           */
X    if(re_exec(fn) != 0) {
X	DEBUG("NOT descending into RCS dir");
X	return 0;
X    }
X
X    /* must run lstat on the file */
X    lstat(fn, sbuf);
X
X    if ((sbuf->st_mode & S_IFMT) == S_IFBLK)
X	return 0;
X    if ((sbuf->st_mode & S_IFMT) == S_IFSOCK)
X	return 0;
X    if ((sbuf->st_mode & S_IFMT) == S_IFIFO)
X	return 0;
X    if ((sbuf->st_mode & S_IFMT) == S_IFCHR)
X	return 0;
X    if ((sbuf->st_mode & S_IFMT) == S_IFDIR) {
X	struct stat xbuf;
X
X	if (dir_ignore)
X	    return 0;
X
X	stat(fn, &xbuf);
X	/* if this is a link to a directory, ignore */
X	if ((xbuf.st_mode & S_IFMT) == S_IFLNK) {
X	    return 0;
X	}
X    }
X    if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
X	if (link_ignore)
X	    return 0;
X    }
X
X    if ((sbuf->st_mode & S_IFMT) == S_IFDIR)
X	name = fn + strlen(head);
X    else
X	name = fn + strlen(head) + 1;
X
X    if(strlen(name) == 0)
X	return 0;	/* original dir call, ignore */
X
X    switch (ftype) {
X    default:
X	fprintf(out_fp, "echo Unknown");
X	fprintf(out_fp, "\t%s", fn);
X	fprintf(out_fp, "\t%s", name);
X	fprintf(out_fp, "\n");
X	break;
X    case FTW_F:
X	if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
X	    fprintf(out_fp, "ln -s");
X	    fprintf(out_fp, "\t%s", fn);
X	    fprintf(out_fp, "\t%s", name);
X	    fprintf(out_fp, "\n");
X	} else {
X	    fprintf(out_fp, "ln -s ");
X	    fprintf(out_fp, "\t%s", fn);
X	    fprintf(out_fp, "\t%s", name);
X	    fprintf(out_fp, "\n");
X	}
X	if(debug)fprintf(stderr, "ln -s\t%s\t%s\n", fn, name);
X	break;
X    case FTW_D:
X	/* RCS is always a link to the real base system              */
X	/* have to look at the LAST 4 char of name for RCS directory */
X	/* The RCS directory will be linked rather than mkdir'ed     */
X	temp = name + strlen(name) - 4;
X	if(strcmp(temp, "/RCS") == 0){
X	    /* if this link already exists, do NOT do it again, */
X	    /* an other link will lead to a loop in the master  */
X	    /* tree.                                            */
X	    char lbuf[BUFSIZ];
X	    if((status = readlink(name+1, lbuf, BUFSIZ)) >= 0)
X		return 0;
X
X	    sprintf(buf, "ln -s %s %s", fn, name + 1);
X	    if(debug)
X		fprintf(stderr, "%s\n", buf);
X
X	    if ((sbuf->st_mode & S_IFMT) == S_IFDIR) {
X		struct stat xbuf;
X
X		stat(fn, &xbuf);
X		/* if this is a link to a directory, ignore */
X		if ((xbuf.st_mode & S_IFMT) == S_IFLNK) {
X		    fprintf("%d:link to existing directory ignored:%s\n",
X			__FILE__, buf);
X		    return 0;
X		}
X	    }
X	}
X	else{
X	    char xbuf[512];
X	    strcpy(xbuf, name+1);
X	    sprintf(buf, "mkdir %s", xbuf);
X	    if(debug)fprintf(stderr, "%s\n", xbuf);
X	}
X
X#define FOR_REAL	1
X#if FOR_REAL
X	/* NEVER create a loop by linking with a file containing RCS/RCS */
X	if(strsearch(buf, "RCS/RCS") == 0)
X	    system(buf);
X#else
X	fprintf(out_fp, "%s\n", buf);
X#endif
X	break;
X    case FTW_NS:
X	fprintf(stderr, "stat_failed");
X	fprintf(stderr, "\t%s", fn);
X	fprintf(stderr, "\t%s", name);
X	fprintf(stderr, "\n");
X	break;
X    }
X    return 0;
X}
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X    int save_argc;
X    int status;
X    char *prog_name;
X    char *s;
X    char buf[BUFSIZ];
X    char sys_cmd[BUFSIZ];
X    char *rcs_base_fn;
X
X    /* parse the necessary options */
X    save_argc = argc;
X    prog_name = *argv;
X    while (--argc > 0 && (*++argv)[0] == '-') {
X	for (s = argv[0] + 1; *s != '\0'; s++) {
X	    switch (*s) {
X	    default:
X	    case '?':
X		fprintf(stderr, "usage: %s base_dir\n", prog_name);
X		fprintf(stderr, "\t-Ifile = use \"file\" as an include in fgrep\n");
X		fprintf(stderr, "\t-Xfile = use \"file\" as an exlcude in fgrep\n");
X		exit(1);
X	    case 'd':
X		debug = 1;
X		break;
X	    case 'I':
X		incl_fn = ++s;
X		while (*++s);	/* to end of string */
X		--s;		/* but not too far */
X		break;
X	    case 'X':
X		excl_fn = ++s;
X		while (*++s);	/* to end of string */
X		--s;		/* but not too far */
X		break;
X	    }
X	}
X    }
X
X    /* compile regular expression to prevent descent into */
X    /* an RCS linked subdirectory.                        */
X    if(head = (char *)re_comp("\/RCS\/")){
X	printf(head);
X	exit(1);
X    }
X
X    temp_fn = (char *) mktemp("TTXXXXXX");
X    out_fp = fopen(temp_fn, "w");
X
X    /* this may or may not exist */
X    if(argc == 0) {
X	head = rcs_root_fn = (char *)getenv("ROOT_TREE");
X    }
X    else
X	head = *argv;		/* get the head */
X
X
X    {
X	int i;
X
X	/* move the environmental varialbe to a work area */
X	for(i = 0; i < sizeof(head_buf); head_buf[i++] = '\0');
X	strcpy(head_buf, head);
X	head = head_buf;
X    }
X
X    /* prove existence/readability of RCS root file */
X    if ((fp = fopen(rcs_root_fn, "r")) == (FILE *) 0) {
X	fprintf(stderr, "Cannot open $ROOT_TREE %s\n", rcs_base_fn);
X	exit(1);
X    }
X    fclose(fp);
X
X    /* open the exclusion file, if none check under the ROOT_TREE dir */
X    if ((excl_fp = fopen(excl_fn, "r")) == (FILE *) 0) {
X	sprintf(excl_fn = sys_cmd, "%s/excl_file", rcs_root_fn);
X	excl_fn = strdup(sys_cmd);	/* save this string */
X	if ((excl_fp = fopen(sys_cmd, "r")) == (FILE *) 0) {
X	    fprintf(stderr, "Cannot read file exclusion list: %s\n",
X		excl_fn);
X	    exit(1);
X	}
X    }
X
X    /* open the inclusion file, if none check under the ROOT_TREE dir */
X    if ((incl_fp = fopen(incl_fn, "r")) == (FILE *) 0) {
X	sprintf(sys_cmd, "%s/incl_file", rcs_root_fn);
X	incl_fn = strdup(sys_cmd);	/* save this string */
X	if ((incl_fp = fopen(sys_cmd, "r")) == (FILE *) 0) {
X	    fprintf(stderr, "Cannot read file inclusion list: %s\n",
X		incl_fn);
X	    exit(1);
X	}
X    }
X
X    ftw(rcs_root_fn, print_file, 20);
X
X    rewind(out_fp);
X    sprintf(sys_cmd, "cat %s", temp_fn);
X    if (incl_fp) {
X	sprintf(buf, "| fgrep -f %s ", incl_fn);
X	strcat(sys_cmd, buf);
X    }
X    if (excl_fp) {
X	sprintf(buf, "| fgrep -v -f %s ", excl_fn);
X	strcat(sys_cmd, buf);
X    }
X    strcat(sys_cmd, " | /bin/csh ");
X    /* Do not remove temp file if debugging */
X    if(!debug) {
X	strcat(sys_cmd, "; rm ");
X	strcat(sys_cmd, temp_fn);
X    }
X    printf("%s\n", sys_cmd);
X    exit(system(sys_cmd));
X}
END_OF_FILE
echo shar: 1 control character may be missing from \"'lktree.c'\"
if test 8287 -ne `wc -c <'lktree.c'`; then
    echo shar: \"'lktree.c'\" unpacked with wrong size!
fi
# end of 'lktree.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0