[net.sources] squish.c

smk (12/26/82)

/*	Squish - compacts directory.
 *
 *	Compile:	cc -O -s squish.c -ldir -o squish
 *	Run:		when directory is quiescent (AS ROOT)
 *
 *			..!linus!smk Dec 82
 *			from an idea by RJKing WECo-Mg6565 Dec 82
 */


#include	<stdio.h>
#include	<signal.h>
#include	<fstab.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<ndir.h>


#define	MAKE_DIRECTORY	"mkdir"
#define	REMOVE	"rm -r"


char   *mktemp ();
char   *index ();
char   *rindex ();
char   *sprintf ();
FILE * popen ();
struct fstab   *getfsent ();

void compact ();
void legal ();
void remake_directory ();
void fix_directory ();
void activate ();

char   *cmdname;


main (argc, argv)
int     argc;
char   *argv[];
{
    register int    search;
    register int    scan;
    register int    files;
    int     verbose;
    int     recursive;

    cmdname = argv[0];

    verbose = 0;
    recursive = 0;
    files = 0;

    if (argc == 1)
    {
	fprintf (stderr, "Usage: %s [-v] [-r] [directories ...]\n",
		cmdname);
	exit (1);
    }

 /* 	Check for command line flags and also for legal file names.	 
 */
    for (search = 1; search < argc; search++)
	switch (argv[search][0])
	{
	    case '-': 
		for (scan = 1; scan < strlen (argv[search]); scan++)
		    switch (argv[search][scan])
		    {
			case 'v': 
			    verbose = 1;
			    break;
			case 'r': 
			    recursive = 1;
			    break;
			default: 
			    fprintf (stderr,
				    "Usage: %s [-v] [-r] [directories ...]\n",
				    cmdname);
			    exit (1);
			    break;
		    }
		break;
	    default: 
		if (access (argv[search], 0) != 0)
		{
		    fprintf (stderr, "%s: %s does not exist\n",
			    cmdname, argv[search]);
		    exit (1);
		}
		else
		{
		/* 	Could be dangerous to allow .. and . in paths.	 
		*/
		    legal (argv[search]);
		    files = 1;
		}
		break;
	}

    if (!files)
    {
	fprintf (stderr, "Usage: %s [-v] [-r] [directories ...]\n",
		cmdname);
	exit (1);
    }

 /* 	Some of the link operations can only be done by root.	 */
    if (getuid () != 0)
    {
	fprintf (stderr, "%s: not superuser\n", cmdname);
	exit (1);
    }

 /* 	Do the actual work -- compact for each argument.	 */
    for (search = 1; search < argc; search++)
	switch (argv[search][0])
	{
	    case '-': 
		break;
	    default: 
		if (verbose)
		    fprintf (stdout, "squishing directory %s\n",
			    argv[search]);

	    /* 	Since some operations in compact() will leave the file
	       system in a strange state, don't allow interrupts duing
	       this (and restore them later).	 */
		(void) signal (SIGHUP, SIG_IGN);
		(void) signal (SIGINT, SIG_IGN);
		(void) signal (SIGQUIT, SIG_IGN);
		(void) signal (SIGALRM, SIG_IGN);
		(void) signal (SIGTERM, SIG_IGN);

		compact (argv[search], 1, verbose, recursive);

		(void) signal (SIGHUP, SIG_DFL);
		(void) signal (SIGINT, SIG_DFL);
		(void) signal (SIGQUIT, SIG_DFL);
		(void) signal (SIGALRM, SIG_DFL);
		(void) signal (SIGTERM, SIG_DFL);
		break;
	}
}



/*	This is the workhorse routine.  It is recursive if the
	-r flag is set.  It essentially shifts directories and
	remakes a clean directory so null directory entries are
	removed.  (UNIX doesn't do this and otherwise would let
	directories grow without bound.)  When this is performed
	on a directory, the directory becomes as small as possible
	without losing any information.  There should be a system
	call for this.	*/
void compact (directory, level, verbose, recursive)
char   *directory;
int     level;
int     verbose;
int     recursive;
{
    register int    indent;
    register struct direct *entry;
    register    DIR * temp_directory;
    char   *last_component;
    struct stat buffer;
    char    trash[BUFSIZ];
    char    newfile[BUFSIZ];
    char    parent[BUFSIZ];

 /* 	Since we can't be in the directory we are about to change (i.e.,
    remove), we go back to the parent directory.	 */
    (void) sprintf (parent, "%s", directory);
    last_component = rindex (parent, '/');
    if (last_component == NULL)
    {
	fprintf (stderr, "%s: must use full pathname\n", cmdname);
	exit (1);
    }
    else
	if (last_component == parent)
	    (void) sprintf (parent, "/");
	else
	    *last_component = '\0';

    if (chdir (parent) < 0)
    {
	fprintf (stderr, "%s: cannot chdir to ", cmdname);
	perror (parent);
	exit (1);
    }


 /* 	Make the temporary place where we move the directory. Search until
    a non-existing name is found there.	 */
    do
    {
	(void) sprintf (trash, "%s/squish%d.XXXXXX", parent, level);
	(void) sprintf (trash, "%s", mktemp (trash));
    }
    while (access (trash, 0) == 0);


 /* 	This is dangerous unless you know what is happening. We are
    linking DIRECTORIES!  This messes up the file system link count
    temporarily.	 */
    if (link (directory, trash) < 0)
    {
	fprintf (stderr, "%s: cannot link %s to ",
		cmdname, directory);
	perror (trash);
	exit (1);
    }

 /* 	Now the link count can be restored by removing one of the
    directories.	 */
    if (unlink (directory) < 0)
    {
	fprintf (stderr, "%s: cannot unlink ", cmdname);
	perror (directory);
	exit (1);
    }

 /* 	The old directory is remade in the normal UNIX way. A by-product
    is that is is empty -- the key to this.	 */
    remake_directory (directory, trash);

    if (chdir (trash) < 0)
    {
	fprintf (stderr, "%s: cannot chdir to ", cmdname);
	perror (trash);
	(void) link (trash, directory);
	(void) unlink (trash);
	exit (1);
    }


 /* 	If -r was specified, we now look in the directory for
    subdirectories on which to call this routine recursively. If not, only
    the top level directory is compacted.	 */
    if (recursive)
    {
	temp_directory = opendir (".");
	if (temp_directory == NULL)
	{
	    fprintf (stderr, "%s: cannot open ", cmdname);
	    perror (trash);
	    (void) link (trash, directory);
	    (void) unlink (trash);
	    exit (1);
	}

	entry = readdir (temp_directory);
	while (entry != NULL)
	{
	    if (strcmp (entry -> d_name, ".") &&
		    strcmp (entry -> d_name, ".."))
	    {
		if (stat (entry -> d_name, &buffer) != 0)
		{
		    fprintf (stderr, "%s: cannot stat ", cmdname);
		    perror (entry -> d_name);
		}
		else
		    if ((buffer.st_mode & S_IFMT) == S_IFDIR)
		    {
			(void) sprintf (newfile, "%s/%s",
				trash, entry -> d_name);
			if (verbose)
			{
			    for (indent = 0; indent < level; indent++)
				fprintf (stdout, "    ");
			    fprintf (stdout,
				    "squishing subdirectory %s\n",
				    newfile);
			}
			compact (newfile, level + 1,
				verbose, recursive);
		    }
	    }
	    entry = readdir (temp_directory);
	}
	closedir (temp_directory);

    }


 /* 	Here we go!  Basically, we deal with all entries except . and ..
    and move them from the temporary directory to the (clean) old one.	 
 */
    temp_directory = opendir (".");
    if (temp_directory == NULL)
    {
	fprintf (stderr, "%s: cannot open ", cmdname);
	perror (trash);
	(void) link (trash, directory);
	(void) unlink (trash);
	exit (1);
    }

    entry = readdir (temp_directory);
    while (entry != NULL)
    {
	if (strcmp (entry -> d_name, ".") &&
		strcmp (entry -> d_name, ".."))
	{
	    (void) sprintf (newfile, "%s/%s",
		    directory, entry -> d_name);
	    if (verbose)
	    {
		for (indent = 0; indent < level; indent++)
		    fprintf (stdout, "    ");
		fprintf (stdout, "mv %s %s\n", entry -> d_name,
			newfile);
	    }

	/* 	Subdirectories can't just be linked/unlinked, because
	   their contents are dependent on context. This kludge fixes it.
	   However, for a short period, we have a subdirectory that has a
	   wrong .. entry!	 */
	    fix_directory (entry -> d_name, directory);

	    if (link (entry -> d_name, newfile) < 0)
	    {
		fprintf (stderr, "%s: cannot link %s to ",
			cmdname, entry -> d_name);
		perror (newfile);
	    }
	    if (unlink (entry -> d_name) < 0)
	    {
		fprintf (stderr, "%s: cannot unlink ", cmdname);
		perror (entry -> d_name);
	    }
	}
	entry = readdir (temp_directory);
    }
    closedir (temp_directory);

    if (chdir (parent) < 0)
    {
	fprintf (stderr, "%s: cannot return to ", cmdname);
	perror (parent);
	exit (1);
    }


 /* 	This temporary (now clean, but big) directory can now be junked.
    Everything at this point is OK with file system integrity, so we can
    use the normal UNIX way to do it.	 */
    activate (REMOVE, trash);
}



/*	We require only full pathnames with no intervening
	. or .. or double /'s just to insure to a degree that
	no one is actively using that directory.  Out of
	the kernel, we really can't do this completely.  There
	is also a problem if one of the subdirectories is
	mountable, so we use fstab to check on this.  However,
	we can't check for 'spontaneous' mounts that don't
	use fstab.	*/
void legal (directory)
register char  *directory;
{
    register char  *place;
    register struct fstab  *file_system;
    struct stat buffer;


 /* 	Check for legal file name syntax.	 */
    place = directory;
    do
    {
	place++;

	if ((strlen (place) == 0) ||
		!strncmp (place, "./", strlen ("./")) ||
		!strncmp (place, "../", strlen ("../")) ||
		!strncmp (place, "/", strlen ("/")) ||
		!strcmp (place, ".") || !strcmp (place, ".."))
	{
	    fprintf (stderr,
		    "%s: 'x/', '//', '.', and '..' illegal (in %s)\n",
		    cmdname, directory);
	    exit (1);
	}

	place = index (place, '/');
    }
    while (place != NULL);

    if (stat (directory, &buffer) != 0)
    {
	fprintf (stderr, "%s: cannot stat ", cmdname);
	perror (directory);
    }
    else
	if ((buffer.st_mode & S_IFMT) != S_IFDIR)
	{
	    fprintf (stderr, "%s: %s is not a directory\n",
		    cmdname, directory);
	    exit (1);
	}


 /* 	Now check to see if something at or below this level is a
    mountable file system.	 */
    setfsent ();

    file_system = getfsent ();
    while (file_system != NULL)
    {
	if ((!strcmp (file_system -> fs_type, FSTAB_RW) ||
		    !strcmp (file_system -> fs_type, FSTAB_RO) ||
		    !strcmp (file_system -> fs_type, "um")) &&
		(strlen (directory) <= strlen (file_system -> fs_file)))
	    if (!strncmp (directory, file_system -> fs_file,
			strlen (directory)))
	    {
		fprintf (stderr,
			"%s: %s contains the %s file system\n",
			cmdname, directory, file_system -> fs_file);
		exit (1);
	    }
	file_system = getfsent ();
    }

    endfsent ();
}



/*	Create a new directory with the mode/owner/group
	of the original.	*/
void remake_directory (original_directory, temp_directory)
char   *original_directory;
char   *temp_directory;
{
    struct stat buffer;

    if (stat (temp_directory, &buffer) != 0)
    {
	fprintf (stderr, "%s: cannot stat ", cmdname);
	perror (temp_directory);
	exit (1);
    }

    activate (MAKE_DIRECTORY, original_directory);

    if (chown (original_directory,
		(int) buffer.st_uid, (int) buffer.st_gid) < 0)
    {
	fprintf (stderr, "%s: cannot chown ", cmdname);
	perror (original_directory);
    }

    if (chmod (original_directory, (int) (buffer.st_mode & ~S_IFMT)) < 0)
    {
	fprintf (stderr, "%s: cannot chmod ", cmdname);
	perror (original_directory);
    }
}



/*	Nice little routine to execute a UNIX command that
	doesn't have any terminal I/O.	*/
void activate (command_name, argument)
register char  *command_name;
register char  *argument;
{
    register    FILE * execute;
    char    command[BUFSIZ];

    (void) sprintf (command, "%s %s", command_name, argument);
    execute = popen (command, "w");
    if (execute == NULL)
    {
	fprintf (stderr, "%s: could not execute '%s'\n",
		cmdname, command);
	exit (1);
    }
    if (pclose (execute) != 0)
    {
	fprintf (stderr, "%s: execution of '%s' failed\n",
		cmdname, command);
	exit (1);
    }
}


/*	Unfortunately, we need this to make the .. entry
	in subdirectories point to the correct entry.
	Beforehand, it points to the temporary directory
	(correctly).  Afterward, it points to the remade
	directory (incorrectly).  Howver, the next operation
	will be a link/unlink combination that will make
	this whole thing kosher.	*/
void fix_directory (entry, new_directory)
register char  *entry;
register char  *new_directory;
{
    struct stat buffer;
    char    new_link[BUFSIZ];

    if (stat (entry, &buffer) == 0)
	if ((buffer.st_mode & S_IFMT) == S_IFDIR)
	{
	    (void) sprintf (new_link, "%s/..", entry);
	    if (unlink (new_link) == 0)
		(void) link (new_directory, new_link);
	}
}