[comp.sources.misc] utility to find changed files

warren@pluto.UUCP (Warren Burstein) (08/13/87)

changed searches for files underneath specified directories that have
changed after a given date.  It does not check subdirectories on
other filesystems.  I use it to find out what files have changed
on my root and /usr filesystems without seeing everything under /.

Usage:  changed mmddyy directories

The code that parses mmddyy and produces the number of seconds
since Jan 1, 1970 should be useful in other places, too.

Warren Burstein (pluto!warren) 8/11/87

No doubt I could have done it in find(1) but it took less time
to write this than to figure it out.
-----------cut somewhere else, why don't you?-----------------

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# Read.me Makefile changed.c

echo x - Read.me
cat > "Read.me" << '//E*O*F Read.me//'
This directory makes the "changed" program.

It searches for files underneath specified directories that have
changed after a given date.  It does not check subdirectories on
other filesystems.  I use it to find out what files have changed
on my root and /usr filesystems without seeing everything under /.

Usage:  changed mmddyy directories

The code that parses mmddyy and produces the number of seconds
since Jan 1, 1970 should be useful in other places, too.

Warren Burstein (pluto!warren) 8/11/87
//E*O*F Read.me//

echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
changed: changed.c
	cc changed.c -o changed

lint:
	lint changed.c

shar:
	shar Read.me Makefile changed.c > shar

clean:
	-rm -f *.o core a.out changed
//E*O*F Makefile//

echo x - changed.c
cat > "changed.c" << '//E*O*F changed.c//'
/*
 * This program expects args mmddyy dir1 dir2 ....
 *
 * For each directory named, all files under that directory recusively
 * that are newer than the specified date will be listed on stdout, but
 * it does not check subdirectories that are not on the same device as
 * their parent directory.
 *
 * It should be useful if you want to find out what files on your
 * root and usr devices have been changed since your last upgrade without
 * listing every user file, unless you keep users on /usr.
 *
 * Warren Burstein (pluto!warren) 7/22/87
 */

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <sys/param.h>

char *progname;

main(argc, argv)
char **argv;
{
	int arg;
	long date, parse_date();
	char cwd[MAXPATHLEN];

	progname = argv[0];

	if (argc < 3) {
		fprintf(stderr, "usage: %s mmddyy dir...\n", progname);
		exit(1);
	}

	date = parse_date(argv[1]);

	getwd(cwd);
	for (arg = 2; arg < argc; arg++) {
		/*
		 * look_at may change working directory, so come back
		 * to where we started if this directory arg doesn't start
		 * at /.  Don't bother if this is the first time thru here.
		 */
		if (arg != 2 && *argv[arg] != '/' && chdir(cwd) < 0) {
			fprintf(stderr, "%s: cannot return to %s: ", progname, cwd);
			perror("");
			exit(1);
		}
		look_at(argv[arg], date);
	}

	exit(0);
}

/*
 * Turn a string that looks like mmddyy into a date in the format
 * returned by time(2), seconds since 00:00:00 GMT Jan 1, 1970.
 */

static int months[13] = {
	0,							/* let January be 1, not zero */
    31, 28, 31, 30, 31, 30,
	31, 31, 30, 31, 30, 31
};

long parse_date(mmddyy)
char *mmddyy;
{
	int month, day, year;
	int m;
	long date;
	struct timeval tv;
	struct timezone tz;

	(void) gettimeofday(&tv, &tz);

	if (sscanf(mmddyy, "%2d%2d%2d", &month, &day, &year) != 3) {
		fprintf(stderr, "%s: date must be in mmddyy format\n", progname);
		exit(1);
	}

	if (year < 70) {
		fprintf(stderr, "%s: the world was created in 1970!\n", progname);
		exit(1);
	}

	/*
	 * Adjust February
	 */
	months[2] = is_leap(year + 1900) ? 29 : 28;

	if (month < 1 || month > 12) {
		fprintf(stderr, "%s: month must be between 1 and 12\n", progname);
		exit(1);
	}

	if (day < 1 || day > months[month]) {
		fprintf(stderr, "%s: day must be between 1 and %d\n",
			   progname, months[month]);
		exit(1);
	}

	/*
	 * Find days between Jan 1, 1970 and Jan 1 of year (ignore Julian
	 * corrections here), add days in months and day in month.
	 */
	date = (year - 70) * 365 + year / 4 - 70 / 4;
	for (m = 1; m < month; m++)
	  date += months[m];
	date += day - 1;

	/*
	 * Turn into seconds, correct for longitude and daylight savings time.
	 */
	date *= 24 * 60 *60;
	date += tz.tz_minuteswest * 60;
	if (localtime(&date)->tm_isdst)
	  date -= 60 * 60;

	return date;
}

/*
 * Return true if year is a leap year.
 */
int is_leap(year) {
  return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}

/*
 * List all entries in dir that are newer than date.  Recurse on
 * subdirectories that are on the same device as dir.  Treat symbolic
 * links like regular files - list them if the link is new, not the file
 * referenced.
 *
 * Exit on any error since it's a pain to figure out what the current
 * directory is afterwards.
 */

look_at(dir, date)
char *dir;
long date;
{
	DIR *dirp;
	struct direct *dp;
	struct stat st;
	dev_t dev;

	if (stat(dir, &st) < 0) {
		fprintf(stderr, "%s: cannot stat directory %s\n", progname, dir);
		exit(1);
	}
	dev = st.st_dev;

	if ( (dirp = opendir(dir)) == NULL) {
		fprintf(stderr, "%s: cannot open directory %s\n", progname, dir);
		exit(1);
	}

	if (chdir(dir) < 0) {
		fprintf(stderr, "%s: cannot chdir to %s\n", progname, dir);
		exit(1);
	}

	while ( (dp = readdir(dirp)) != NULL) {
		/*
		 * Skip self and parent
		 */
		if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
		  continue;

		/*
		 * Use lstat, so as not to follow symbolic links.
		 */
		if (lstat(dp->d_name, &st) < 0) {
			fprintf(stderr, "cannot stat file %s/%s\n", dir, dp->d_name);
			continue;
		}

		if ((st.st_mode & S_IFMT) == S_IFDIR) {
			if (st.st_dev == dev) {
				char dirname[MAXPATHLEN];

				/*
				 * look_at chdir's to a subdirectory, go back up
				 * when done.
				 */
				(void) sprintf(dirname, "%s/%s", dir, dp->d_name);
				look_at(dirname, date);
				(void) chdir("..");
			}
		} else if (st.st_mtime > date)
		  printf("%s/%s\n", dir, dp->d_name);
	}
	(void) closedir(dirp);
}
//E*O*F changed.c//

exit 0
-- 
/|/~\~~\    The entire world             Warren Burstein
 |__/__/_/  is a very strange carrot.
 |          But the farmer               philabs!tg!pluto!warren
/           is not afraid at all.        Why doesn't life come with subtitles?