Blame libmisc/walk_tree.c

rpm-build 0a0c83
/*
rpm-build 0a0c83
  File: walk_tree.c
rpm-build 0a0c83
rpm-build 0a0c83
  Copyright (C) 2007 Andreas Gruenbacher <a.gruenbacher@computer.org>
rpm-build 0a0c83
rpm-build 0a0c83
  This program is free software; you can redistribute it and/or modify it under
rpm-build 0a0c83
  the terms of the GNU Lesser General Public License as published by the
rpm-build 0a0c83
  Free Software Foundation; either version 2.1 of the License, or (at
rpm-build 0a0c83
  your option) any later version.
rpm-build 0a0c83
rpm-build 0a0c83
  This program is distributed in the hope that it will be useful, but WITHOUT
rpm-build 0a0c83
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
rpm-build 0a0c83
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
rpm-build 0a0c83
  License for more details.
rpm-build 0a0c83
rpm-build 0a0c83
  You should have received a copy of the GNU Lesser General Public
rpm-build 0a0c83
  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
rpm-build 0a0c83
*/
rpm-build 0a0c83
rpm-build 0a0c83
#include "config.h"
rpm-build 0a0c83
#include <sys/types.h>
rpm-build 0a0c83
#include <sys/stat.h>
rpm-build 0a0c83
#include <unistd.h>
rpm-build 0a0c83
#include <sys/time.h>
rpm-build 0a0c83
#include <sys/resource.h>
rpm-build 0a0c83
#include <dirent.h>
rpm-build 0a0c83
#include <stdio.h>
rpm-build 0a0c83
#include <string.h>
rpm-build 0a0c83
#include <errno.h>
rpm-build 0a0c83
rpm-build 0a0c83
#include "walk_tree.h"
rpm-build 0a0c83
rpm-build 0a0c83
struct entry_handle {
rpm-build 0a0c83
	struct entry_handle *prev, *next;
rpm-build 0a0c83
	dev_t dev;
rpm-build 0a0c83
	ino_t ino;
rpm-build 0a0c83
	DIR *stream;
rpm-build 0a0c83
	long pos;
rpm-build 0a0c83
};
rpm-build 0a0c83
rpm-build 0a0c83
static struct entry_handle head = {
rpm-build 0a0c83
	.next = &head,
rpm-build 0a0c83
	.prev = &head,
rpm-build 0a0c83
	/* The other fields are unused. */
rpm-build 0a0c83
};
rpm-build 0a0c83
static struct entry_handle *closed = &head;
rpm-build 0a0c83
static unsigned int num_dir_handles;
rpm-build 0a0c83
rpm-build 0a0c83
static int walk_tree_visited(dev_t dev, ino_t ino)
rpm-build 0a0c83
{
rpm-build 0a0c83
	struct entry_handle *i;
rpm-build 0a0c83
rpm-build 0a0c83
	for (i = head.next; i != &head; i = i->next)
rpm-build 0a0c83
		if (i->dev == dev && i->ino == ino)
rpm-build 0a0c83
			return 1;
rpm-build 0a0c83
	return 0;
rpm-build 0a0c83
}
rpm-build 0a0c83
rpm-build 0a0c83
static int walk_tree_rec(const char *path, int walk_flags,
rpm-build 0a0c83
			 int (*func)(const char *, const struct stat *, int,
rpm-build 0a0c83
				     void *), void *arg, int depth)
rpm-build 0a0c83
{
rpm-build 0a0c83
	int follow_symlinks = (walk_flags & WALK_TREE_LOGICAL) ||
rpm-build 0a0c83
			      ((walk_flags & WALK_TREE_DEREFERENCE) &&
rpm-build 0a0c83
			       !(walk_flags & WALK_TREE_PHYSICAL) &&
rpm-build 0a0c83
			       depth == 0);
rpm-build 0a0c83
	int have_dir_stat = 0, flags = walk_flags, err;
rpm-build 0a0c83
	struct entry_handle dir;
rpm-build 0a0c83
	struct stat st;
rpm-build 0a0c83
rpm-build 0a0c83
	/*
rpm-build 0a0c83
	 * If (walk_flags & WALK_TREE_PHYSICAL), do not traverse symlinks.
rpm-build 0a0c83
	 * If (walk_flags & WALK_TREE_LOGICAL), traverse all symlinks.
rpm-build 0a0c83
	 * Otherwise, traverse only top-level symlinks.
rpm-build 0a0c83
	 */
rpm-build 0a0c83
	if (depth == 0)
rpm-build 0a0c83
		flags |= WALK_TREE_TOPLEVEL;
rpm-build 0a0c83
rpm-build 0a0c83
	if (lstat(path, &st) != 0)
rpm-build 0a0c83
		return func(path, NULL, flags | WALK_TREE_FAILED, arg);
rpm-build 0a0c83
	if (S_ISLNK(st.st_mode)) {
rpm-build 0a0c83
		flags |= WALK_TREE_SYMLINK;
rpm-build 0a0c83
		if ((flags & WALK_TREE_DEREFERENCE) ||
rpm-build 0a0c83
		    ((flags & WALK_TREE_TOPLEVEL) &&
rpm-build 0a0c83
		     (flags & WALK_TREE_DEREFERENCE_TOPLEVEL))) {
rpm-build 0a0c83
			if (stat(path, &st) != 0)
rpm-build 0a0c83
				return func(path, NULL,
rpm-build 0a0c83
					    flags | WALK_TREE_FAILED, arg);
rpm-build 0a0c83
			dir.dev = st.st_dev;
rpm-build 0a0c83
			dir.ino = st.st_ino;
rpm-build 0a0c83
			have_dir_stat = 1;
rpm-build 0a0c83
		}
rpm-build 0a0c83
	} else if (S_ISDIR(st.st_mode)) {
rpm-build 0a0c83
		dir.dev = st.st_dev;
rpm-build 0a0c83
		dir.ino = st.st_ino;
rpm-build 0a0c83
		have_dir_stat = 1;
rpm-build 0a0c83
	}
rpm-build 0a0c83
	err = func(path, &st, flags, arg);
rpm-build 0a0c83
rpm-build 0a0c83
	/*
rpm-build 0a0c83
	 * Recurse if WALK_TREE_RECURSIVE and the path is:
rpm-build 0a0c83
	 *      a dir not from a symlink
rpm-build 0a0c83
	 *      a link and follow_symlinks
rpm-build 0a0c83
	 */
rpm-build 0a0c83
        if ((flags & WALK_TREE_RECURSIVE) &&
rpm-build 0a0c83
	    ((!(flags & WALK_TREE_SYMLINK) && S_ISDIR(st.st_mode)) ||
rpm-build 0a0c83
	     ((flags & WALK_TREE_SYMLINK) && follow_symlinks))) {
rpm-build 0a0c83
		struct dirent *entry;
rpm-build 0a0c83
rpm-build 0a0c83
		/*
rpm-build 0a0c83
		 * Check if we have already visited this directory to break
rpm-build 0a0c83
		 * endless loops.
rpm-build 0a0c83
		 *
rpm-build 0a0c83
		 * If we haven't stat()ed the file yet, do an opendir() for
rpm-build 0a0c83
		 * figuring out whether we have a directory, and check whether
rpm-build 0a0c83
		 * the directory has been visited afterwards. This saves a
rpm-build 0a0c83
		 * system call for each non-directory found.
rpm-build 0a0c83
		 */
rpm-build 0a0c83
		if (have_dir_stat && walk_tree_visited(dir.dev, dir.ino))
rpm-build 0a0c83
			return err;
rpm-build 0a0c83
rpm-build 0a0c83
		if (num_dir_handles == 0 && closed->prev != &head) {
rpm-build 0a0c83
close_another_dir:
rpm-build 0a0c83
			/* Close the topmost directory handle still open. */
rpm-build 0a0c83
			closed = closed->prev;
rpm-build 0a0c83
			closed->pos = telldir(closed->stream);
rpm-build 0a0c83
			closedir(closed->stream);
rpm-build 0a0c83
			closed->stream = NULL;
rpm-build 0a0c83
			num_dir_handles++;
rpm-build 0a0c83
		}
rpm-build 0a0c83
rpm-build 0a0c83
		dir.stream = opendir(path);
rpm-build 0a0c83
		if (!dir.stream) {
rpm-build 0a0c83
			if (errno == ENFILE && closed->prev != &head) {
rpm-build 0a0c83
				/* Ran out of file descriptors. */
rpm-build 0a0c83
				num_dir_handles = 0;
rpm-build 0a0c83
				goto close_another_dir;
rpm-build 0a0c83
			}
rpm-build 0a0c83
rpm-build 0a0c83
			/*
rpm-build 0a0c83
			 * PATH may be a symlink to a regular file, or a dead
rpm-build 0a0c83
			 * symlink which we didn't follow above.
rpm-build 0a0c83
			 */
rpm-build 0a0c83
			if (errno != ENOTDIR && errno != ENOENT)
rpm-build 0a0c83
				err += func(path, NULL, flags |
rpm-build 0a0c83
							WALK_TREE_FAILED, arg);
rpm-build 0a0c83
			return err;
rpm-build 0a0c83
		}
rpm-build 0a0c83
rpm-build 0a0c83
		/* See walk_tree_visited() comment above... */
rpm-build 0a0c83
		if (!have_dir_stat) {
rpm-build 0a0c83
			if (stat(path, &st) != 0)
rpm-build 0a0c83
				goto skip_dir;
rpm-build 0a0c83
			dir.dev = st.st_dev;
rpm-build 0a0c83
			dir.ino = st.st_ino;
rpm-build 0a0c83
			if (walk_tree_visited(dir.dev, dir.ino))
rpm-build 0a0c83
				goto skip_dir;
rpm-build 0a0c83
		}
rpm-build 0a0c83
rpm-build 0a0c83
		/* Insert into the list of handles. */
rpm-build 0a0c83
		dir.next = head.next;
rpm-build 0a0c83
		dir.prev = &head;
rpm-build 0a0c83
		dir.prev->next = &dir;
rpm-build 0a0c83
		dir.next->prev = &dir;
rpm-build 0a0c83
		num_dir_handles--;
rpm-build 0a0c83
rpm-build 0a0c83
		while ((entry = readdir(dir.stream)) != NULL) {
rpm-build 0a0c83
			char *path_end;
rpm-build 0a0c83
rpm-build 0a0c83
			if (!strcmp(entry->d_name, ".") ||
rpm-build 0a0c83
			    !strcmp(entry->d_name, ".."))
rpm-build 0a0c83
				continue;
rpm-build 0a0c83
			path_end = strchr(path, 0);
rpm-build 0a0c83
			if ((path_end - path) + strlen(entry->d_name) + 1 >=
rpm-build 0a0c83
			    FILENAME_MAX) {
rpm-build 0a0c83
				errno = ENAMETOOLONG;
rpm-build 0a0c83
				err += func(path, NULL,
rpm-build 0a0c83
					    flags | WALK_TREE_FAILED, arg);
rpm-build 0a0c83
				continue;
rpm-build 0a0c83
			}
rpm-build 0a0c83
			*path_end++ = '/';
rpm-build 0a0c83
			strcpy(path_end, entry->d_name);
rpm-build 0a0c83
			err += walk_tree_rec(path, walk_flags, func, arg,
rpm-build 0a0c83
					     depth + 1);
rpm-build 0a0c83
			*--path_end = 0;
rpm-build 0a0c83
			if (!dir.stream) {
rpm-build 0a0c83
				/* Reopen the directory handle. */
rpm-build 0a0c83
				dir.stream = opendir(path);
rpm-build 0a0c83
				if (!dir.stream)
rpm-build 0a0c83
					return err + func(path, NULL, flags |
rpm-build 0a0c83
						    WALK_TREE_FAILED, arg);
rpm-build 0a0c83
				seekdir(dir.stream, dir.pos);
rpm-build 0a0c83
rpm-build 0a0c83
				closed = closed->next;
rpm-build 0a0c83
				num_dir_handles--;
rpm-build 0a0c83
			}
rpm-build 0a0c83
		}
rpm-build 0a0c83
rpm-build 0a0c83
		/* Remove from the list of handles. */
rpm-build 0a0c83
		dir.prev->next = dir.next;
rpm-build 0a0c83
		dir.next->prev = dir.prev;
rpm-build 0a0c83
		num_dir_handles++;
rpm-build 0a0c83
rpm-build 0a0c83
	skip_dir:
rpm-build 0a0c83
		if (closedir(dir.stream) != 0)
rpm-build 0a0c83
			err += func(path, NULL, flags | WALK_TREE_FAILED, arg);
rpm-build 0a0c83
	}
rpm-build 0a0c83
	return err;
rpm-build 0a0c83
}
rpm-build 0a0c83
rpm-build 0a0c83
int walk_tree(const char *path, int walk_flags, unsigned int num,
rpm-build 0a0c83
	      int (*func)(const char *, const struct stat *, int, void *),
rpm-build 0a0c83
	      void *arg)
rpm-build 0a0c83
{
rpm-build 0a0c83
	char path_copy[FILENAME_MAX];
rpm-build 0a0c83
rpm-build 0a0c83
	num_dir_handles = num;
rpm-build 0a0c83
	if (num_dir_handles < 1) {
rpm-build 0a0c83
		struct rlimit rlimit;
rpm-build 0a0c83
rpm-build 0a0c83
		num_dir_handles = 1;
rpm-build 0a0c83
		if (getrlimit(RLIMIT_NOFILE, &rlimit) == 0 &&
rpm-build 0a0c83
		    rlimit.rlim_cur >= 2)
rpm-build 0a0c83
			num_dir_handles = rlimit.rlim_cur / 2;
rpm-build 0a0c83
	}
rpm-build 0a0c83
	if (strlen(path) >= FILENAME_MAX) {
rpm-build 0a0c83
		errno = ENAMETOOLONG;
rpm-build 0a0c83
		return func(path, NULL, WALK_TREE_FAILED, arg);
rpm-build 0a0c83
	}
rpm-build 0a0c83
	strcpy(path_copy, path);
rpm-build 0a0c83
	return walk_tree_rec(path_copy, walk_flags, func, arg, 0);
rpm-build 0a0c83
}