/* filesys.c - core analysis suite
 *
 * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
 * Copyright (C) 2002, 2003, 2004, 2005, 2006 David Anderson
 * Copyright (C) 2002, 2003, 2004, 2005, 2006 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "defs.h"
#include <linux/major.h>

static void show_mounts(ulong, int, struct task_context *);
static int find_booted_kernel(void);
static int find_booted_system_map(void);
static int verify_utsname(char *);
static char **build_searchdirs(int, int *);
static int redhat_kernel_directory_v1(char *);
static int redhat_kernel_directory_v2(char *);
static int redhat_debug_directory(char *);
static int file_dump(ulong, ulong, ulong, int, int);
static ulong *create_dentry_array(ulong, int *);
static void show_fuser(char *, char *);
static int mount_point(char *);
static int open_file_reference(struct reference *);
static void memory_source_init(void);
static int get_pathname_component(ulong, ulong, int, char *, char *);
static ulong *get_mount_list(int *, struct task_context *);
char *inode_type(char *, char *);
static void match_proc_version(void);
static void get_live_memory_source(void);
static int memory_driver_module_loaded(int *);
static int insmod_memory_driver_module(void);
static int get_memory_driver_dev(dev_t *);
static int memory_driver_init(void);
static int create_memory_device(dev_t);
static void *radix_tree_lookup(ulong, ulong, int);

#define DENTRY_CACHE (20)
#define INODE_CACHE  (20)
#define FILE_CACHE   (20)

static struct filesys_table {
        char *dentry_cache;
	ulong cached_dentry[DENTRY_CACHE];
	ulong cached_dentry_hits[DENTRY_CACHE];
	int dentry_cache_index;
	ulong dentry_cache_fills;

        char *inode_cache;
        ulong cached_inode[INODE_CACHE];
        ulong cached_inode_hits[INODE_CACHE];
        int inode_cache_index;
        ulong inode_cache_fills;

        char *file_cache;
        ulong cached_file[FILE_CACHE];
        ulong cached_file_hits[FILE_CACHE];
        int file_cache_index;
        ulong file_cache_fills;

} filesys_table = { 0 };


static struct filesys_table *ft = &filesys_table;

#define DUMP_FULL_NAME   1
#define DUMP_INODE_ONLY  2
#define DUMP_DENTRY_ONLY 4

/*
 *  Open the namelist, dumpfile and output devices.
 */
void
fd_init(void)
{
	pc->nfd = pc->kfd = pc->mfd = pc->dfd = -1;

        if ((pc->nullfp = fopen("/dev/null", "w+")) == NULL)
                error(INFO, "cannot open /dev/null (for extraneous output)");

	if (REMOTE()) 
		remote_fd_init();
	else {
		if (pc->namelist && pc->namelist_debug && pc->system_map) {
			error(INFO, 
                "too many namelist options:\n       %s\n       %s\n       %s\n",
				pc->namelist, pc->namelist_debug, 
				pc->system_map);
			program_usage(SHORT_FORM);
		}

		if (pc->namelist) {
			if (!pc->dumpfile && !get_proc_version())
	                	error(INFO, "/proc/version: %s\n", 
					strerror(errno));
		} else {
			if (pc->dumpfile) {
				error(INFO, "namelist argument required\n");
				program_usage(SHORT_FORM);
			}
			if (!find_booted_kernel())
	                	program_usage(SHORT_FORM);
		}
	
		if (!pc->dumpfile) {
			pc->flags |= LIVE_SYSTEM;
			get_live_memory_source();
		}
	
		if ((pc->nfd = open(pc->namelist, O_RDONLY)) < 0) 
			error(FATAL, "%s: %s\n", pc->namelist, strerror(errno));
		else {
			close(pc->nfd);
			pc->nfd = -1;
		}

		if (ACTIVE() && !(pc->namelist_debug || pc->system_map)) {
			memory_source_init();
			match_proc_version();
		}
	
	}

	memory_source_init();
}

/*
 *  Do whatever's necessary to handle the memory source.
 */
static void
memory_source_init(void)
{
	if (REMOTE() && !(pc->flags & MEMSRC_LOCAL))
		return;

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return;

        if (ACTIVE()) {
		if (pc->mfd != -1)  /* already been here */
			return;

		if (!STREQ(pc->live_memsrc, "/dev/mem") &&
		     STREQ(pc->live_memsrc, pc->memory_device)) {
			if (memory_driver_init())
				return;

			error(INFO, "cannot initialize crash memory driver\n");
			error(INFO, "using /dev/mem\n\n");
			pc->flags &= ~MEMMOD;
			pc->flags |= DEVMEM;
			pc->readmem = read_dev_mem;
			pc->writemem = write_dev_mem;
			pc->live_memsrc = "/dev/mem";
		} 

		if (STREQ(pc->live_memsrc, "/dev/mem")) {
	                if ((pc->mfd = open("/dev/mem", O_RDWR)) < 0) {
	                        if ((pc->mfd = open("/dev/mem", O_RDONLY)) < 0)
	                                error(FATAL, "/dev/mem: %s\n",
	                                        strerror(errno));
	                } else
	                        pc->flags |= MFD_RDWR;
		} else
			error(FATAL, "unknown memory device: %s\n",
				pc->live_memsrc);

		return;
        } 

	if (pc->dumpfile) {
	        if (!file_exists(pc->dumpfile, NULL))
	        	error(FATAL, "%s: %s\n", pc->dumpfile, 
				strerror(ENOENT));
	
		if (!(pc->flags & DUMPFILE_TYPES)) 
			error(FATAL, "%s: dump format not supported!\n",
				pc->dumpfile);
	
                if (pc->flags & NETDUMP) {
                        if (!netdump_init(pc->dumpfile, fp))
                                error(FATAL, "%s: initialization failed\n",
                                        pc->dumpfile);
		} else if (pc->flags & KDUMP) {
                        if (!kdump_init(pc->dumpfile, fp))
                                error(FATAL, "%s: initialization failed\n",
                                        pc->dumpfile);
		} else if (pc->flags & XENDUMP) {
                        if (!xendump_init(pc->dumpfile, fp))
                                error(FATAL, "%s: initialization failed\n",
                                        pc->dumpfile);
		} else if (pc->flags & DISKDUMP) {
                        if (!diskdump_init(pc->dumpfile, fp))
                                error(FATAL, "%s: initialization failed\n",
                                        pc->dumpfile);
                } else if (pc->flags & LKCD) {
	        	if ((pc->dfd = open(pc->dumpfile, O_RDONLY)) < 0)
	                	error(FATAL, "%s: %s\n", pc->dumpfile, 
					strerror(errno));
			if (!lkcd_dump_init(fp, pc->dfd, pc->dumpfile))
	                	error(FATAL, "%s: initialization failed\n", 
					pc->dumpfile);
		} else if (pc->flags & S390D) { 
			if (!s390_dump_init(pc->dumpfile))
				error(FATAL, "%s: initialization failed\n",
                                        pc->dumpfile);
		}
	}
}

/*
 *  If only a namelist argument is entered for a live system, and the
 *  version string doesn't match /proc/version, try to avert a failure
 *  by assigning it to a matching System.map.
 */
static void
match_proc_version(void)
{
	char command[BUFSIZE];
	char buffer[BUFSIZE];
	FILE *pipe;
	int found;

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return;

	if (!strlen(kt->proc_version)) 
		return;

        sprintf(command, "/usr/bin/strings %s", pc->namelist);
        if ((pipe = popen(command, "r")) == NULL) {
                error(INFO, "%s: %s\n", pc->namelist, strerror(errno));
                return;
        }

	found = FALSE;
        while (fgets(buffer, BUFSIZE-1, pipe)) {
		char* ptr;
		ptr = strstr(buffer, "Linux version 2.");
		if (!ptr)
			continue;

                if (STREQ(ptr, kt->proc_version)) 
                	found = TRUE;
		break;
        }
        pclose(pipe);

	if (found) {
                if (CRASHDEBUG(1)) {
			fprintf(fp, "/proc/version:\n%s", kt->proc_version);
			fprintf(fp, "%s:\n%s", pc->namelist, buffer);
		}
		return;
	}

	error(WARNING, "%s%sand /proc/version do not match!\n\n", 
		pc->namelist, 
		strlen(pc->namelist) > 39 ? "\n         " : " ");

	if (find_booted_system_map())
                pc->flags |= SYSMAP;
}


#define CREATE  1
#define DESTROY 0
#define DEFAULT_SEARCHDIRS 5

static char **
build_searchdirs(int create, int *preferred)
{
	int i;
	int cnt, start;
	DIR *dirp;
        struct dirent *dp;
	char dirbuf[BUFSIZE];
	static char **searchdirs = { 0 };
	static char *default_searchdirs[DEFAULT_SEARCHDIRS+1] = {
        	"/usr/src/linux/",
        	"/boot/",
	        "/boot/efi/redhat",
		"/boot/efi/EFI/redhat",
        	"/",
        	NULL
	};

	if (!create) {
		if (searchdirs) {
			for (i = DEFAULT_SEARCHDIRS; searchdirs[i]; i++) 
				free(searchdirs[i]);
			free(searchdirs);
		}
		return NULL;
	}

	if (preferred)
		*preferred = 0;

	/*
	 *  Allow, at a minimum, the defaults plus an extra three directories 
	 *  for the two possible /usr/src/redhat/BUILD/kernel-xxx locations 
	 *  plus the Red Hat debug directory.
	 */  
	cnt = DEFAULT_SEARCHDIRS + 3;  

        if ((dirp = opendir("/usr/src"))) {
                for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) 
			cnt++;

		if ((searchdirs = calloc(cnt, sizeof(char *))) == NULL) {
			error(INFO, "/usr/src/ directory list malloc: %s\n",
                                strerror(errno));
			closedir(dirp);
			return default_searchdirs;
		} 

		for (i = 0; i < DEFAULT_SEARCHDIRS; i++) 
			searchdirs[i] = default_searchdirs[i];
		cnt = DEFAULT_SEARCHDIRS;

		rewinddir(dirp);

        	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
			if (STREQ(dp->d_name, "linux") ||
			    STREQ(dp->d_name, "redhat") ||
			    STREQ(dp->d_name, ".") ||
			    STREQ(dp->d_name, ".."))
				continue;

			sprintf(dirbuf, "/usr/src/%s", dp->d_name);
			if (mount_point(dirbuf))
				continue;
			if (!is_directory(dirbuf))
				continue;

			if ((searchdirs[cnt] = (char *)
			    malloc(strlen(dirbuf)+2)) == NULL) {
				error(INFO,
				    "/usr/src/ directory entry malloc: %s\n",
                                	strerror(errno));
				break;
			}
			sprintf(searchdirs[cnt], "%s/", dirbuf); 
			cnt++;
		}

		closedir(dirp);

		searchdirs[cnt] = NULL;
	} else {
		if ((searchdirs = calloc(cnt, sizeof(char *))) == NULL) {
			error(INFO, "search directory list malloc: %s\n",
                                strerror(errno));
			closedir(dirp);
			return default_searchdirs;
		} 
		for (i = 0; i < DEFAULT_SEARCHDIRS; i++) 
			searchdirs[i] = default_searchdirs[i];
		cnt = DEFAULT_SEARCHDIRS;
	}

        if (redhat_kernel_directory_v1(dirbuf)) {
                if ((searchdirs[cnt] = (char *) 
		    malloc(strlen(dirbuf)+2)) == NULL) {
                        error(INFO, 
			    "/usr/src/redhat directory entry malloc: %s\n",
                        	strerror(errno));
                } else {
                        sprintf(searchdirs[cnt], "%s/", dirbuf);
                        cnt++;
                }
        }

        if (redhat_kernel_directory_v2(dirbuf)) {
                if ((searchdirs[cnt] = (char *)
                    malloc(strlen(dirbuf)+2)) == NULL) {
                        error(INFO,
                            "/usr/src/redhat directory entry malloc: %s\n",
                                strerror(errno));
                } else {
                        sprintf(searchdirs[cnt], "%s/", dirbuf);
                        cnt++;
                }
        }

        if (redhat_debug_directory(dirbuf)) {
                if ((searchdirs[cnt] = (char *)
                     malloc(strlen(dirbuf)+2)) == NULL) {
                         error(INFO, "%s directory entry malloc: %s\n",
                                 dirbuf, strerror(errno));
                } else {
                         sprintf(searchdirs[cnt], "%s/", dirbuf);
			if (preferred)
				*preferred = cnt;
                         cnt++;
                }
        }

	searchdirs[cnt] = NULL;
 
	if (CRASHDEBUG(1)) {
		i = start = preferred ? *preferred : 0;
		do {
			fprintf(fp, "searchdirs[%d]: %s\n", 
				i, searchdirs[i]);
			if (++i == cnt) {
				if (start != 0)
					i = 0;
				else
					break;
			}
		} while (i != start);
	}

	return searchdirs;
}

static int
redhat_kernel_directory_v1(char *buf)
{
	char *p1, *p2;

	if (!strstr(kt->proc_version, "Linux version "))
		return FALSE;

	BZERO(buf, BUFSIZE);
	sprintf(buf, "/usr/src/redhat/BUILD/kernel-");

	p1 = &kt->proc_version[strlen("Linux version ")];
	p2 = &buf[strlen(buf)];

	while (((*p1 >= '0') && (*p1 <= '9')) || (*p1 == '.'))
		*p2++ = *p1++;	

	strcat(buf, "/linux");
	return TRUE;
}

static int
redhat_kernel_directory_v2(char *buf)
{
        char *p1, *p2;

        if (!strstr(kt->proc_version, "Linux version "))
                return FALSE;

        BZERO(buf, BUFSIZE);
        sprintf(buf, "/usr/src/redhat/BUILD/kernel-");

        p1 = &kt->proc_version[strlen("Linux version ")];
        p2 = &buf[strlen(buf)];

        while (((*p1 >= '0') && (*p1 <= '9')) || (*p1 == '.'))
                *p2++ = *p1++;

        strcat(buf, "/linux-");

        p1 = &kt->proc_version[strlen("Linux version ")];
        p2 = &buf[strlen(buf)];

        while (((*p1 >= '0') && (*p1 <= '9')) || (*p1 == '.'))
                *p2++ = *p1++;

        return TRUE;
}


static int
redhat_debug_directory(char *buf)
{
        char *p1, *p2;

        if (!strstr(kt->proc_version, "Linux version "))
                return FALSE;

        BZERO(buf, BUFSIZE);
        sprintf(buf, "%s/", pc->redhat_debug_loc);

        p1 = &kt->proc_version[strlen("Linux version ")];
        p2 = &buf[strlen(buf)];

        while (*p1 != ' ')
                *p2++ = *p1++;

        return TRUE;
}

/*
 *  If a namelist was not entered, presume we're using the currently-running
 *  kernel.  Read its version string from /proc/version, and then look in
 *  the search directories for a kernel with the same version string embedded
 *  in it.
 */
static int
find_booted_kernel(void)
{
	char kernel[BUFSIZE];
	char command[BUFSIZE];
	char buffer[BUFSIZE];
	char **searchdirs;
	int i, preferred, wrapped;
        DIR *dirp;
        struct dirent *dp;
	FILE *pipe;
	int found;

	pc->flags |= FINDKERNEL;

	fflush(fp);

	if (!file_exists("/proc/version", NULL)) {
		error(INFO, 
		    "/proc/version: %s: cannot determine booted kernel\n",
			strerror(ENOENT));
		return FALSE;
	}

	if (!get_proc_version()) {
                error(INFO, "/proc/version: %s\n", strerror(errno));
                return FALSE;
	}

        if (CRASHDEBUG(1))
                fprintf(fp, "\nfind_booted_kernel: search for [%s]\n", 
			kt->proc_version);

        searchdirs = build_searchdirs(CREATE, &preferred);

	for (i = preferred, wrapped = found = FALSE; !found; i++) { 
		if (!searchdirs[i]) {
			if (preferred && !wrapped) {
				wrapped = TRUE;
				i = 0;
			} else
				break;
		} else if (wrapped && (preferred == i))
			break;
	
	        dirp = opendir(searchdirs[i]);
		if (!dirp)
			continue;
	        for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
			if (dp->d_name[0] == '.')
				continue;

			sprintf(kernel, "%s%s", searchdirs[i], dp->d_name);

			if (mount_point(kernel) ||
			    !file_readable(kernel) || 
                            !is_elf_file(kernel))
				continue;

			sprintf(command, "/usr/bin/strings %s", kernel);
	        	if ((pipe = popen(command, "r")) == NULL) {
	        		error(INFO, "%s: %s\n", 
					kernel, strerror(errno));
				continue;
			}

			if (CRASHDEBUG(1)) 
				fprintf(fp, "find_booted_kernel: check: %s\n", 
					kernel);

			while (fgets(buffer, BUFSIZE-1, pipe)) {
				if (STREQ(buffer, kt->proc_version)) {
					found = TRUE;
					break;
				}
			}
			pclose(pipe);
	
			if (found)
				break;
	        }
		closedir(dirp);
	}

	mount_point(DESTROY);
	build_searchdirs(DESTROY, NULL);

	if (found) {
                if ((pc->namelist = (char *)malloc
		    (strlen(kernel)+1)) == NULL) 
			error(FATAL, "booted kernel name malloc: %s\n",
				strerror(errno));
                else {
                        strcpy(pc->namelist, kernel);
			if (CRASHDEBUG(1))
				fprintf(fp, "find_booted_kernel: found: %s\n", 
					pc->namelist);
                        return TRUE;
                }
	}

	error(INFO, 
             "cannot find booted kernel -- please enter namelist argument\n\n");
	return FALSE;
}

/*
 *  Determine whether a file is a mount point, without the benefit of stat().
 *  This horrendous kludge is necessary to avoid uninterruptible stat() or 
 *  fstat() calls on nfs mount-points where the remote directory is no longer 
 *  available.
 */
static int
mount_point(char *name)
{
	int i;
	static int mount_points_gathered = -1;
	static char **mount_points;
        char *arglist[MAXARGS];
	char buf[BUFSIZE];
	char mntfile[BUFSIZE];
	int argc, found;
        FILE *mp;

	/*
	 *  The first time through, stash a list of mount points.
	 */

	if (mount_points_gathered < 0) {
		found = mount_points_gathered = 0; 

        	if (file_exists("/proc/mounts", NULL))
			sprintf(mntfile, "/proc/mounts");
		else if (file_exists("/etc/mtab", NULL))
			sprintf(mntfile, "/etc/mtab");
		else
                	return FALSE;

        	if ((mp = fopen(mntfile, "r")) == NULL)
                	return FALSE;

		while (fgets(buf, BUFSIZE, mp)) {
        		argc = parse_line(buf, arglist);
			if (argc < 2)
				continue;
			found++;
		}
		pclose(mp);

		if (!(mount_points = (char **)malloc(sizeof(char *) * found)))
			return FALSE;

                if ((mp = fopen(mntfile, "r")) == NULL) 
                        return FALSE;

		i = 0;
                while (fgets(buf, BUFSIZE, mp) && 
		       (mount_points_gathered < found)) {
                        argc = parse_line(buf, arglist);
                        if (argc < 2)
                                continue;
			if ((mount_points[i] = (char *)
			     malloc(strlen(arglist[1])*2))) { 
				strcpy(mount_points[i], arglist[1]);
                        	mount_points_gathered++, i++;
			}
                }
        	pclose(mp);

		if (CRASHDEBUG(2))
			for (i = 0; i < mount_points_gathered; i++)
				fprintf(fp, "mount_points[%d]: %s (%lx)\n", 
					i, mount_points[i], 
					(ulong)mount_points[i]);
		
	}

	/*
	 *  A null name string means we're done with this routine forever,
	 *  so the malloc'd memory can be freed.
	 */
        if (!name) {   
                for (i = 0; i < mount_points_gathered; i++) 
                        free(mount_points[i]);
                free(mount_points);
                return FALSE;
        }


	for (i = 0; i < mount_points_gathered; i++) {
		if (STREQ(name, mount_points[i]))
			return TRUE;
	}


        return FALSE;
}


/*
 *  If /proc/version exists, get it for verification purposes later.
 */
int
get_proc_version(void)
{
        FILE *version;

	if (strlen(kt->proc_version))  /* been here, done that... */
		return TRUE;

        if (!file_exists("/proc/version", NULL)) 
                return FALSE;

        if ((version = fopen("/proc/version", "r")) == NULL) 
                return FALSE;

        if (fread(&kt->proc_version, sizeof(char), 
	    	BUFSIZE-1, version) <= 0) 
                return FALSE;
        
        fclose(version);

	return TRUE;
}


/*
 *  Given a non-matching kernel namelist, try to find a System.map file
 *  that has a system_utsname whose contents match /proc/version.
 */
static int
find_booted_system_map(void)
{
	char system_map[BUFSIZE];
	char **searchdirs;
	int i;
        DIR *dirp;
        struct dirent *dp;
	int found;

	fflush(fp);

	if (!file_exists("/proc/version", NULL)) {
		error(INFO, 
		    "/proc/version: %s: cannot determine booted System.map\n",
			strerror(ENOENT));
		return FALSE;
	}

	if (!get_proc_version()) {
                error(INFO, "/proc/version: %s\n", strerror(errno));
                return FALSE;
	}

	found = FALSE;

	/*
	 *  To avoid a search, try the obvious first.
	 */
	sprintf(system_map, "/boot/System.map");
	if (file_readable(system_map) && verify_utsname(system_map)) {
		found = TRUE;
	} else {
	        searchdirs = build_searchdirs(CREATE, NULL);
	
		for (i = 0; !found && searchdirs[i]; i++) { 
		        dirp = opendir(searchdirs[i]);
			if (!dirp)
				continue;
		        for (dp = readdir(dirp); dp != NULL; 
			     dp = readdir(dirp)) {
				if (!strstr(dp->d_name, "System.map"))
					continue;
	
				sprintf(system_map, "%s%s", searchdirs[i], 
					dp->d_name);
	
				if (mount_point(system_map) ||
				    !file_readable(system_map) || 
	                            !is_system_map(system_map))
					continue;
	
				if (verify_utsname(system_map)) {
					found = TRUE;
					break;
				}
		        }
			closedir(dirp);
		}

		mount_point(DESTROY);
		build_searchdirs(DESTROY, NULL);
	}

	if (found) {
                if ((pc->system_map = (char *)malloc
		    (strlen(system_map)+1)) == NULL) 
			error(FATAL, "booted system map name malloc: %s\n",
				strerror(errno));
                strcpy(pc->system_map, system_map);
		if (CRASHDEBUG(1))
			fprintf(fp, "find_booted_system_map: found: %s\n", 
				pc->system_map);
                return TRUE;
	}

	error(INFO, 
 "cannot find booted system map -- please enter namelist or system map\n\n");
	return FALSE;
}

/*
 *  Read the system_utsname from /dev/mem, based upon the address found
 *  in the passed-in System.map file, and compare it to /proc/version.
 */
static int
verify_utsname(char *system_map)
{
	char command[BUFSIZE];
	char buffer[BUFSIZE];
	FILE *pipe;
	int found;
	ulong value;
	struct new_utsname new_utsname;

	sprintf(command, "/usr/bin/strings %s", system_map);
       	if ((pipe = popen(command, "r")) == NULL) 
		return FALSE;
	
	if (CRASHDEBUG(1)) 
		fprintf(fp, "verify_utsname: check: %s\n", system_map);

	found = FALSE;
	while (fgets(buffer, BUFSIZE-1, pipe)) {
		if (strstr(buffer, "D system_utsname")) {
			found = TRUE;
			break;
		}
	}
	pclose(pipe);

	if (!found)
		return FALSE;
	
	if (extract_hex(buffer, &value, NULLCHAR, TRUE) &&
	    (READMEM(pc->mfd, &new_utsname, 
	     sizeof(struct new_utsname), value, 
	     VTOP(value)) > 0) && 
	    ascii_string(new_utsname.release) &&
	    ascii_string(new_utsname.version) &&
	    STRNEQ(new_utsname.release, "2.") &&
	    (strlen(new_utsname.release) > 4) &&
	    (strlen(new_utsname.version) > 27)) {
		if (CRASHDEBUG(1)) {
			fprintf(fp, "release: [%s]\n", new_utsname.release);
			fprintf(fp, "version: [%s]\n", new_utsname.version);
		}
		if (strstr(kt->proc_version, new_utsname.release) &&
		    strstr(kt->proc_version, new_utsname.version)) {
			return TRUE;
		}
	}

	return FALSE;
}

/*
 *  Determine whether a file exists, using the caller's stat structure if
 *  one was passed in.
 */
int
file_exists(char *file, struct stat *sp)
{
        struct stat sbuf;

        if (stat(file, sp ? sp : &sbuf) == 0)
                return TRUE;

        return FALSE;
}

/*
 *  Determine whether a file exists, and if so, if it's readable.
 */
int 
file_readable(char *file)
{
	long tmp;
	int fd;

	if (!file_exists(file, NULL))
		return FALSE;

	if ((fd = open(file, O_RDONLY)) < 0) 
		return FALSE;

	if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
		close(fd);
		return FALSE;
	}
	close(fd);

	return TRUE;
}

/*
 *  Quick file checksummer.
 */
int 
file_checksum(char *file, long *retsum)
{
	int i;
	int fd;
	ssize_t cnt;
	char buf[MIN_PAGE_SIZE];
	long csum;


	if ((fd = open(file, O_RDONLY)) < 0)
		return FALSE;

	csum = 0;
	BZERO(buf, MIN_PAGE_SIZE);
	while ((cnt = read(fd, buf, MIN_PAGE_SIZE)) > 0) {
		for (i = 0; i < cnt; i++)
			csum += buf[i];
		BZERO(buf, MIN_PAGE_SIZE);
	}
	close(fd);

	*retsum = csum;

	return TRUE;
}

int
is_directory(char *file)
{
    struct stat sbuf;
 
    if (!file || !strlen(file))
        return(FALSE);

    if (stat(file, &sbuf) == -1)
        return(FALSE);                         /* This file doesn't exist. */
            
    return((sbuf.st_mode & S_IFMT) == S_IFDIR ? TRUE : FALSE);
}


/*
 *  Search a directory tree for filename, and if found, return a temporarily
 *  allocated buffer containing the full pathname.   The "done" business is
 *  protection against fgets() prematurely returning NULL before the find
 *  command completes.  (I thought this was impossible until I saw it happen...)
 *  When time permits, rewrite this doing the search by hand.
 */
char *
search_directory_tree(char *directory, char *file)
{
	char command[BUFSIZE];
	char buf[BUFSIZE];
	char *retbuf;
	FILE *pipe;
	int done;

	if (!file_exists("/usr/bin/find", NULL) || 
	    !file_exists("/bin/echo", NULL) ||
	    !is_directory(directory) ||
	    (*file == '(')) 
		return NULL;

	sprintf(command, 
            "/usr/bin/find %s -name %s -print; /bin/echo search done",
		directory, file);

        if ((pipe = popen(command, "r")) == NULL) {
                error(INFO, "%s: %s\n", command, strerror(errno));
                return NULL;
        }

	done = FALSE;
	retbuf = NULL;

        while (fgets(buf, BUFSIZE-1, pipe) || !done) {
                if (STREQ(buf, "search done\n")) {
                        done = TRUE;
                        break;
                }
                if (!retbuf &&
                    STREQ((char *)basename(strip_linefeeds(buf)), file)) {
                        retbuf = GETBUF(strlen(buf)+1);
                        strcpy(retbuf, buf);
                }
        }

        pclose(pipe);
	return retbuf;
}
 
/*
 *  Determine whether a file exists, and if so, if it's a tty.
 */
int
is_a_tty(char *filename)
{
        int fd;

        if ((fd = open(filename, O_RDONLY)) < 0)
                return FALSE;

        if (isatty(fd)) {
                close(fd);
                return TRUE;
        }

        close(fd);
        return FALSE;
}

/*
 *  Open a tmpfile for command output.  fp is stashed in pc->saved_fp, and
 *  temporarily set to the new FILE pointer.  This allows a command to still
 *  print to the original output while the tmpfile is still open.
 */

#define OPEN_ONLY_ONCE 

#ifdef OPEN_ONLY_ONCE
void
open_tmpfile(void)
{
        if (pc->tmpfile)
                error(FATAL, "recursive temporary file usage\n");

	if (!pc->tmp_fp) {
        	if ((pc->tmp_fp = tmpfile()) == NULL) 
                	error(FATAL, "cannot open temporary file\n");
	}

	fflush(pc->tmpfile);
	ftruncate(fileno(pc->tmp_fp), 0);
	rewind(pc->tmp_fp);

	pc->tmpfile = pc->tmp_fp;
	pc->saved_fp = fp;
	fp = pc->tmpfile;
}
#else
void
open_tmpfile(void)
{
        if (pc->tmpfile)
                error(FATAL, "recursive temporary file usage\n");

        if ((pc->tmpfile = tmpfile()) == NULL) {
                error(FATAL, "cannot open temporary file\n");
        } else {
                pc->saved_fp = fp;
                fp = pc->tmpfile;
        }
}
#endif

/*
 *  Destroy the reference to the tmpfile, and restore fp to the state
 *  it had when open_tmpfile() was called.
 */
#ifdef OPEN_ONLY_ONCE
void
close_tmpfile(void)
{
	if (pc->tmpfile) {
		fflush(pc->tmpfile);
		ftruncate(fileno(pc->tmpfile), 0);
		rewind(pc->tmpfile);
		pc->tmpfile = NULL;
		fp = pc->saved_fp;
	} else 
		error(FATAL, "trying to close an unopened temporary file\n");
}
#else
void
close_tmpfile(void)
{
        if (pc->tmpfile) {
                fp = pc->saved_fp;
                fclose(pc->tmpfile);
                pc->tmpfile = NULL;
        } else
                error(FATAL, "trying to close an unopened temporary file\n");

}
#endif

/*
 *  open_tmpfile2() and close_tmpfile2() do not use a permanent tmpfile, 
 *  and do NOT modify the global fp pointer or pc->saved_fp.  That being the 
 *  case, all wrapped functions must be aware of it, or fp has to manipulated
 *  by the calling function.  The secondary tmpfile should only be used by
 *  common functions that might be called by a higher-level function using
 *  the primary permanent tmpfile.
 */
void 
open_tmpfile2(void)
{
        if (pc->tmpfile2)
                error(FATAL, "recursive secondary temporary file usage\n");
                
        if ((pc->tmpfile2 = tmpfile()) == NULL)
                error(FATAL, "cannot open secondary temporary file\n");
        
        rewind(pc->tmpfile2);
}

void
close_tmpfile2(void)
{
	if (pc->tmpfile2) {
		fflush(pc->tmpfile2);
		fclose(pc->tmpfile2);
        	pc->tmpfile2 = NULL;
	}
}


#define MOUNT_PRINT_INODES  0x1
#define MOUNT_PRINT_FILES   0x2
#define MOUNT_PRINT_ALL (MOUNT_PRINT_INODES|MOUNT_PRINT_FILES)

/*
 *  Display basic information about the currently mounted filesystems.
 *  The -f option lists the open files for the filesystem(s).
 *  The -i option dumps the dirty inodes of the filesystem(s).
 *  If an inode address, vfsmount, superblock, device name or 
 *  directory name is also entered, just show the data for the 
 *  filesystem indicated by the argument.
 */

static char mount_hdr[BUFSIZE] = { 0 };

void
cmd_mount(void)
{
	int i;
	int c, found;
	struct task_context *tc, *namespace_context;
	ulong value;
	char *spec_string;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
        char *arglist[MAXARGS*2];
	ulong vfsmount = 0;
	int flags = 0;
	int mh_flag = 1;
	int save_next;

	namespace_context = pid_to_context(1);

        while ((c = getopt(argcnt, args, "ifn:")) != EOF) {
                switch(c)
		{
		case 'i':
			flags |= MOUNT_PRINT_INODES;
			break;

		case 'f':
			flags |= MOUNT_PRINT_FILES;
			break;

		case 'n':
			switch (str_to_context(optarg, &value, &tc)) {
		        case STR_PID:
                        case STR_TASK:
				namespace_context = tc;
                               	break;
                        case STR_INVALID:
                               	error(FATAL, "invalid task or pid value: %s\n",
                                        	optarg);
                               	break;
			}
			break;

		default:
			argerrs++;
			break;
		}
	}

	if (argerrs)
		cmd_usage(pc->curcmd, SYNOPSIS);

	if (args[optind]) {
		do {
			spec_string = args[optind];

                	if (STRNEQ(spec_string, "0x") && 
			    hexadecimal(spec_string, 0))
                        	shift_string_left(spec_string, 2);

			open_tmpfile();
			show_mounts(0, MOUNT_PRINT_ALL, namespace_context);

			found = FALSE;
        		rewind(pc->tmpfile);
			save_next = 0;
        		while (fgets(buf1, BUFSIZE, pc->tmpfile)) {
				if (STRNEQ(buf1, mount_hdr)) {
					save_next = 1;
					continue;
				}
				if (save_next) {
					strcpy(buf2, buf1);
					save_next = 0;
				}

                		if (!(c = parse_line(buf1, arglist)))
                        		continue;

				for (i = 0; i < c; i++) {
					if (PATHEQ(arglist[i], spec_string))
						found = TRUE;
				}
				if (found) {
					fp = pc->saved_fp;
					if (flags) {
						sscanf(buf2,"%lx",&vfsmount);
						show_mounts(vfsmount, flags, 
							namespace_context);
					} else {
						if (mh_flag) {
							fprintf(fp, mount_hdr);
							mh_flag = 0;
						}
						fprintf(fp, buf2);
					}
					found = FALSE;
					fp = pc->tmpfile;
				}
        		}
			close_tmpfile();
		} while (args[++optind]);
	} else
		show_mounts(0, flags, namespace_context);
}

/*
 *  Do the work for cmd_mount();
 */

static void
show_mounts(ulong one_vfsmount, int flags, struct task_context *namespace_context)
{
	ulong one_vfsmount_list;
	long sb_s_files;
	long s_dirty;
	ulong devp, dirp, sbp, dirty, type, name;
	struct list_data list_data, *ld;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char buf3[BUFSIZE];
	char buf4[BUFSIZE];
	ulong *dentry_list, *dp, *mntlist;
	ulong *vfsmnt;
	char *vfsmount_buf, *super_block_buf;
	ulong dentry, inode, inode_sb, mnt_parent;
	char *dentry_buf, *inode_buf;
	int cnt, i, m, files_header_printed;
	int mount_cnt; 
	static int devlen = 0;
	char mount_files_header[BUFSIZE];

        sprintf(mount_files_header, "%s%s%s%sTYPE%sPATH\n",
                mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "DENTRY"),
                space(MINSPACE),
                mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "INODE"),
                space(MINSPACE),
		space(MINSPACE));
		
	s_dirty = OFFSET(super_block_s_dirty);

	mntlist = 0;
	ld = &list_data;

	if (one_vfsmount) {
		one_vfsmount_list = one_vfsmount;
		mount_cnt = 1;
		mntlist = &one_vfsmount_list;
	} else 
		mntlist = get_mount_list(&mount_cnt, namespace_context); 

	if (!strlen(mount_hdr)) {
		devlen = strlen("DEVNAME");

        	for (m = 0, vfsmnt = mntlist; m < mount_cnt; m++, vfsmnt++) {
                	readmem(*vfsmnt + OFFSET(vfsmount_mnt_devname),
                        	KVADDR, &devp, sizeof(void *),
                        	"vfsmount mnt_devname", FAULT_ON_ERROR);

                	if (read_string(devp, buf1, BUFSIZE-1)) {
				if (strlen(buf1) > devlen)
					devlen = strlen(buf1);
			}
		}

        	snprintf(mount_hdr, sizeof(mount_hdr), "%s %s %s %s DIRNAME\n",
                	mkstring(buf1, VADDR_PRLEN, CENTER, "VFSMOUNT"),
                	mkstring(buf2, VADDR_PRLEN, CENTER, "SUPERBLK"),
                	mkstring(buf3, strlen("devpts"), LJUST, "TYPE"),
			mkstring(buf4, devlen, LJUST, "DEVNAME"));
	}

	if (flags == 0)
		fprintf(fp, "%s", mount_hdr);

	if ((flags & MOUNT_PRINT_FILES) &&
	    (sb_s_files = OFFSET(super_block_s_files)) == INVALID_OFFSET) {
		/*
		 * No open files list in super_block (2.2).  
		 * Use inuse_filps list instead.
		 */
		dentry_list = create_dentry_array(symbol_value("inuse_filps"), 
			&cnt);
	}

	vfsmount_buf = GETBUF(SIZE(vfsmount));
	super_block_buf = GETBUF(SIZE(super_block));

	for (m = 0, vfsmnt = mntlist; m < mount_cnt; m++, vfsmnt++) {
                readmem(*vfsmnt, KVADDR, vfsmount_buf, SIZE(vfsmount),
                    	"vfsmount buffer", FAULT_ON_ERROR);
		
		devp = ULONG(vfsmount_buf +  OFFSET(vfsmount_mnt_devname));

		if (VALID_MEMBER(vfsmount_mnt_dirname)) {
			dirp = ULONG(vfsmount_buf +  
				OFFSET(vfsmount_mnt_dirname)); 
		} else {
			mnt_parent = ULONG(vfsmount_buf + 
				OFFSET(vfsmount_mnt_parent));
			dentry = ULONG(vfsmount_buf +  
				OFFSET(vfsmount_mnt_mountpoint));
		}

		sbp = ULONG(vfsmount_buf + OFFSET(vfsmount_mnt_sb)); 

		if (flags)
			fprintf(fp, mount_hdr);
                fprintf(fp, "%s %s ",
			mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX, 
			MKSTR(*vfsmnt)),
			mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX, 
			MKSTR(sbp)));

                readmem(sbp, KVADDR, super_block_buf, SIZE(super_block),
                        "super_block buffer", FAULT_ON_ERROR);
		type = ULONG(super_block_buf + OFFSET(super_block_s_type)); 
                readmem(type + OFFSET(file_system_type_name),
                        KVADDR, &name, sizeof(void *),
                        "file_system_type name", FAULT_ON_ERROR);

                if (read_string(name, buf1, BUFSIZE-1))
                       fprintf(fp, "%-6s ", buf1);
                else
                       fprintf(fp, "unknown ");

		if (read_string(devp, buf1, BUFSIZE-1)) {
			fprintf(fp, "%s ", mkstring(buf2, devlen, LJUST, buf1));
		} else
			fprintf(fp, "%s ", mkstring(buf2, devlen, LJUST, 
				"(unknown)"));

		if (VALID_MEMBER(vfsmount_mnt_dirname)) {
                	if (read_string(dirp, buf1, BUFSIZE-1))
                        	fprintf(fp, "%-10s\n", buf1);
                	else
                        	fprintf(fp, "%-10s\n", "(unknown)");
		} else {
			get_pathname(dentry, buf1, BUFSIZE, 1, mnt_parent);
                       	fprintf(fp, "%-10s\n", buf1);
		}

		if (flags & MOUNT_PRINT_FILES) {
			if (sb_s_files != -1) {
				/* 
				 * Have list of open files in super_block.
				 */
				dentry_list = 
				    create_dentry_array(sbp+sb_s_files, &cnt);
			}
			files_header_printed = 0;
			for (i=0, dp = dentry_list; i<cnt; i++, dp++) {
				dentry_buf = fill_dentry_cache(*dp);
				inode = ULONG(dentry_buf +
					OFFSET(dentry_d_inode));
				if (!inode)
					continue;
				inode_buf = fill_inode_cache(inode);
				inode_sb = ULONG(inode_buf + 
					OFFSET(inode_i_sb));
				if (inode_sb != sbp)
					continue;
				if (files_header_printed == 0) {
					fprintf(fp, "%s\n",
                                            mkstring(buf2, VADDR_PRLEN,
                                                CENTER, "OPEN FILES"));
					fprintf(fp, mount_files_header);
					files_header_printed = 1;
				}
				file_dump(0, *dp, inode, 0, DUMP_DENTRY_ONLY);
			}
			if (files_header_printed == 0) {
				fprintf(fp, "%s\nNo open files found\n",
					mkstring(buf2, VADDR_PRLEN,
                                            CENTER, "OPEN FILES"));
			} 
		}

		if (flags & MOUNT_PRINT_INODES) {
			dirty = ULONG(super_block_buf + s_dirty); 

			if (dirty != (sbp+s_dirty)) {
				BZERO(ld, sizeof(struct list_data));
                        	ld->flags = VERBOSE;
                        	ld->start = dirty;
                        	ld->end = (sbp+s_dirty);
				ld->header = "DIRTY INODES\n";
				hq_open();
                        	do_list(ld);
				hq_close();
			} else {
				fprintf(fp, 
				    "DIRTY INODES\nNo dirty inodes found\n");
			}
		}

		if (flags && !one_vfsmount)
			fprintf(fp, "\n");

	}

	if (!one_vfsmount)
		FREEBUF(mntlist); 
	FREEBUF(vfsmount_buf);
	FREEBUF(super_block_buf);
}

/*
 *  Allocate and fill a list of the currently-mounted vfsmount pointers.
 */
static ulong *
get_mount_list(int *cntptr, struct task_context *namespace_context)
{
	struct list_data list_data, *ld;
	int mount_cnt;
	ulong *mntlist, namespace, root;
	struct task_context *tc;
	
        ld = &list_data;
        BZERO(ld, sizeof(struct list_data));

	if (symbol_exists("vfsmntlist")) {
        	get_symbol_data("vfsmntlist", sizeof(void *), &ld->start);
               	ld->end = symbol_value("vfsmntlist");
	} else if (VALID_MEMBER(namespace_root)) {
 		tc = namespace_context;

        	readmem(tc->task + OFFSET(task_struct_namespace), KVADDR, 
			&namespace, sizeof(void *), "task namespace", 
			FAULT_ON_ERROR);
        	if (!readmem(namespace + OFFSET(namespace_root), KVADDR, 
			&root, sizeof(void *), "namespace root", 
			RETURN_ON_ERROR|QUIET))
			error(FATAL, "cannot determine mount list location!\n");

		if (CRASHDEBUG(1))
			console("namespace: %lx => root: %lx\n", 
				namespace, root);

        	ld->start = root + OFFSET(vfsmount_mnt_list);
        	ld->end = namespace + OFFSET(namespace_list);
	} else
		error(FATAL, "cannot determine mount list location!\n");
	
        if (VALID_MEMBER(vfsmount_mnt_list)) 
                ld->list_head_offset = OFFSET(vfsmount_mnt_list);
        else 
                ld->member_offset = OFFSET(vfsmount_mnt_next);
        
        hq_open();
        mount_cnt = do_list(ld);
        mntlist = (ulong *)GETBUF(mount_cnt * sizeof(ulong));
        mount_cnt = retrieve_list(mntlist, mount_cnt);
        hq_close();

	*cntptr = mount_cnt;
	return mntlist;
}


/*
 *  Given a dentry, display its address, inode, super_block, pathname.
 */
static void
display_dentry_info(ulong dentry)
{
	int m, found;
        char *dentry_buf, *inode_buf, *vfsmount_buf;
        ulong inode, superblock, sb, vfs;
	ulong *mntlist, *vfsmnt;
	char pathname[BUFSIZE];
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char buf3[BUFSIZE];
	int mount_cnt;

        fprintf(fp, "%s%s%s%s%s%sTYPE%sPATH\n",
                mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "DENTRY"),
                space(MINSPACE),
                mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "INODE"),
                space(MINSPACE),
                mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "SUPERBLK"),
                space(MINSPACE),
		space(MINSPACE));

        dentry_buf = fill_dentry_cache(dentry);
        inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));
	pathname[0] = NULLCHAR;

        if (inode) {
                inode_buf = fill_inode_cache(inode);
                superblock = ULONG(inode_buf + OFFSET(inode_i_sb));
	} else
		superblock = 0;

	if (!inode || !superblock)
		goto nopath;

        if (VALID_MEMBER(file_f_vfsmnt)) {
		mntlist = get_mount_list(&mount_cnt, pid_to_context(1));
        	vfsmount_buf = GETBUF(SIZE(vfsmount));

        	for (m = found = 0, vfsmnt = mntlist; 
		     m < mount_cnt; m++, vfsmnt++) {
                	readmem(*vfsmnt, KVADDR, vfsmount_buf, SIZE(vfsmount),
                        	"vfsmount buffer", FAULT_ON_ERROR);
                	sb = ULONG(vfsmount_buf + OFFSET(vfsmount_mnt_sb));
			if (superblock && (sb == superblock)) {
                		get_pathname(dentry, pathname, 
					BUFSIZE, 1, *vfsmnt);
				found = TRUE;
			}
		}

		if (!found && symbol_exists("pipe_mnt")) {
			get_symbol_data("pipe_mnt", sizeof(long), &vfs);
                        readmem(vfs, KVADDR, vfsmount_buf, SIZE(vfsmount),
                                "vfsmount buffer", FAULT_ON_ERROR);
                        sb = ULONG(vfsmount_buf + OFFSET(vfsmount_mnt_sb));
                        if (superblock && (sb == superblock)) {
                                get_pathname(dentry, pathname, BUFSIZE, 1, vfs);
                                found = TRUE;
                        }
		}
		if (!found && symbol_exists("sock_mnt")) {
			get_symbol_data("sock_mnt", sizeof(long), &vfs);
                        readmem(vfs, KVADDR, vfsmount_buf, SIZE(vfsmount),
                                "vfsmount buffer", FAULT_ON_ERROR);
                        sb = ULONG(vfsmount_buf + OFFSET(vfsmount_mnt_sb));
                        if (superblock && (sb == superblock)) {
                                get_pathname(dentry, pathname, BUFSIZE, 1, vfs);
                                found = TRUE;
                        }
		}
        } else {
		mntlist = 0;
        	get_pathname(dentry, pathname, BUFSIZE, 1, 0);
	}

	if (mntlist) {
		FREEBUF(mntlist);
		FREEBUF(vfsmount_buf);
	}

nopath:
	fprintf(fp, "%s%s%s%s%s%s%s%s%s\n",
		mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX, MKSTR(dentry)),
		space(MINSPACE), 
		mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX, MKSTR(inode)),
		space(MINSPACE),
		mkstring(buf3, VADDR_PRLEN, CENTER|LONG_HEX, MKSTR(superblock)),
		space(MINSPACE), 
		inode ? inode_type(inode_buf, pathname) : "N/A",
		space(MINSPACE), pathname);
}

/*
 *  Return a 4-character type string of an inode, modifying a previously
 *  gathered pathname if necessary.
 */
char *
inode_type(char *inode_buf, char *pathname)
{
	char *type;
        uint32_t umode32;
        uint16_t umode16;
        uint mode;
        ulong inode_i_op;
        ulong inode_i_fop;
	long i_fop_off;

        mode = umode16 = umode32 = 0;

        switch (SIZE(umode_t))
        {
        case SIZEOF_32BIT:
                umode32 = UINT(inode_buf + OFFSET(inode_i_mode));
		mode = umode32;
                break;

        case SIZEOF_16BIT:
                umode16 = USHORT(inode_buf + OFFSET(inode_i_mode));
		mode = (uint)umode16;
                break;
        }

	type = "UNKN";
	if (S_ISREG(mode))
		type = "REG ";
	if (S_ISLNK(mode))
		type = "LNK ";
	if (S_ISDIR(mode))
		type = "DIR ";
	if (S_ISCHR(mode))
		type = "CHR ";
	if (S_ISBLK(mode))
		type = "BLK ";
	if (S_ISFIFO(mode)) {
		type = "FIFO";
		if (symbol_exists("pipe_inode_operations")) {
			inode_i_op = ULONG(inode_buf + OFFSET(inode_i_op));
			if (inode_i_op == 
			    symbol_value("pipe_inode_operations")) {
				type = "PIPE";
				pathname[0] = NULLCHAR;
			}
		} else {
			if (symbol_exists("rdwr_pipe_fops") && 
			    (i_fop_off = OFFSET(inode_i_fop)) > 0) {
				 inode_i_fop = ULONG(inode_buf + i_fop_off);
				 if (inode_i_fop == 
				     symbol_value("rdwr_pipe_fops")) { 
					type = "PIPE";
					pathname[0] = NULLCHAR;
				 }
			}
		}
	}
	if (S_ISSOCK(mode)) {
		type = "SOCK";
		if (STREQ(pathname, "/"))
			pathname[0] = NULLCHAR;
	}

	return type;
}


/*
 *  Walk an open file list and return an array of open dentries.
 */
static ulong *
create_dentry_array(ulong list_addr, int *count)
{ 
	struct list_data list_data, *ld;
	ulong *file, *files_list, *dentry_list;
	ulong dentry, inode;
	char *file_buf, *dentry_buf;
	int cnt, f_count, i;
	int dentry_cnt = 0;

	ld = &list_data;
	BZERO(ld, sizeof(struct list_data));
	readmem(list_addr, KVADDR, &ld->start, sizeof(void *), "file list head",
		FAULT_ON_ERROR);

	if (list_addr == ld->start) {  /* empty list? */
		*count = 0;
		return NULL;
	}

	ld->end = list_addr;
	hq_open();
	cnt = do_list(ld);
	if (cnt == 0) {
		hq_close();
		*count = 0;
		return NULL;
	}
	files_list = (ulong *)GETBUF(cnt * sizeof(ulong));
	cnt = retrieve_list(files_list, cnt);
	hq_close();
	hq_open();

	for (i=0, file = files_list; i<cnt; i++, file++) {
		file_buf = fill_file_cache(*file);

		f_count = INT(file_buf + OFFSET(file_f_count));
		if (!f_count)
			continue;

		dentry = ULONG(file_buf + OFFSET(file_f_dentry));
		if (!dentry)
			continue;

		dentry_buf = fill_dentry_cache(dentry);
		inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));

		if (!inode)
			continue;
		if (hq_enter(dentry))
			dentry_cnt++;
	}
	if (dentry_cnt) {
		dentry_list = (ulong *)GETBUF(dentry_cnt * sizeof(ulong));
		*count = retrieve_list(dentry_list, dentry_cnt);
	} else {
		*count = 0;
		dentry_list = NULL;
	}
	hq_close();
	FREEBUF(files_list);
	return dentry_list;
}

/*
 *  Stash vfs structure offsets
 */
void
vfs_init(void)
{ 
        MEMBER_OFFSET_INIT(nlm_file_f_file, "nlm_file", "f_file");
	MEMBER_OFFSET_INIT(task_struct_files, "task_struct", "files");
	MEMBER_OFFSET_INIT(task_struct_fs, "task_struct", "fs");
	MEMBER_OFFSET_INIT(fs_struct_root, "fs_struct", "root");
	MEMBER_OFFSET_INIT(fs_struct_pwd, "fs_struct", "pwd");
	MEMBER_OFFSET_INIT(fs_struct_rootmnt, "fs_struct", "rootmnt");
	MEMBER_OFFSET_INIT(fs_struct_pwdmnt, "fs_struct", "pwdmnt");
	MEMBER_OFFSET_INIT(files_struct_open_fds_init,  
		"files_struct", "open_fds_init");
	MEMBER_OFFSET_INIT(files_struct_fdt, "files_struct", "fdt");
	if (VALID_MEMBER(files_struct_fdt)) {
		MEMBER_OFFSET_INIT(fdtable_max_fds, "fdtable", "max_fds");
		MEMBER_OFFSET_INIT(fdtable_max_fdset, "fdtable", "max_fdset");
		MEMBER_OFFSET_INIT(fdtable_open_fds, "fdtable", "open_fds");
		MEMBER_OFFSET_INIT(fdtable_fd, "fdtable", "fd");
	} else {
		MEMBER_OFFSET_INIT(files_struct_max_fds, "files_struct", "max_fds");
		MEMBER_OFFSET_INIT(files_struct_max_fdset, "files_struct", "max_fdset");
		MEMBER_OFFSET_INIT(files_struct_open_fds, "files_struct", "open_fds");
		MEMBER_OFFSET_INIT(files_struct_fd, "files_struct", "fd");
	}
	MEMBER_OFFSET_INIT(file_f_dentry, "file", "f_dentry");
	MEMBER_OFFSET_INIT(file_f_vfsmnt, "file", "f_vfsmnt");
	MEMBER_OFFSET_INIT(file_f_count, "file", "f_count");
	MEMBER_OFFSET_INIT(dentry_d_inode, "dentry", "d_inode");
	MEMBER_OFFSET_INIT(dentry_d_parent, "dentry", "d_parent");
	MEMBER_OFFSET_INIT(dentry_d_covers, "dentry", "d_covers");
	MEMBER_OFFSET_INIT(dentry_d_name, "dentry", "d_name");
	MEMBER_OFFSET_INIT(dentry_d_iname, "dentry", "d_iname");
	MEMBER_OFFSET_INIT(inode_i_mode, "inode", "i_mode");
	MEMBER_OFFSET_INIT(inode_i_op, "inode", "i_op");
	MEMBER_OFFSET_INIT(inode_i_sb, "inode", "i_sb");
	MEMBER_OFFSET_INIT(inode_u, "inode", "u");
	MEMBER_OFFSET_INIT(qstr_name, "qstr", "name");
	MEMBER_OFFSET_INIT(qstr_len, "qstr", "len");

	MEMBER_OFFSET_INIT(vfsmount_mnt_next, "vfsmount", "mnt_next");
        MEMBER_OFFSET_INIT(vfsmount_mnt_devname, "vfsmount", "mnt_devname");
        MEMBER_OFFSET_INIT(vfsmount_mnt_dirname, "vfsmount", "mnt_dirname");
        MEMBER_OFFSET_INIT(vfsmount_mnt_sb, "vfsmount", "mnt_sb");
        MEMBER_OFFSET_INIT(vfsmount_mnt_list, "vfsmount", "mnt_list");
        MEMBER_OFFSET_INIT(vfsmount_mnt_parent, "vfsmount", "mnt_parent");
        MEMBER_OFFSET_INIT(vfsmount_mnt_mountpoint, 
		"vfsmount", "mnt_mountpoint");
	MEMBER_OFFSET_INIT(namespace_root, "namespace", "root");
	if (VALID_MEMBER(namespace_root)) {
		MEMBER_OFFSET_INIT(namespace_list, "namespace", "list");
		MEMBER_OFFSET_INIT(task_struct_namespace, 
			"task_struct", "namespace");
	} else if (THIS_KERNEL_VERSION >= LINUX(2,4,20)) {
		if (CRASHDEBUG(2))
			fprintf(fp, "hardwiring namespace stuff\n");
		ASSIGN_OFFSET(task_struct_namespace) = OFFSET(task_struct_files) +
			sizeof(void *);
		ASSIGN_OFFSET(namespace_root) = sizeof(void *);
		ASSIGN_OFFSET(namespace_list) = sizeof(void *) * 2;
	}

        MEMBER_OFFSET_INIT(super_block_s_dirty, "super_block", "s_dirty");
        MEMBER_OFFSET_INIT(super_block_s_type, "super_block", "s_type");
        MEMBER_OFFSET_INIT(file_system_type_name, "file_system_type", "name");
	MEMBER_OFFSET_INIT(super_block_s_files, "super_block", "s_files");
        MEMBER_OFFSET_INIT(inode_i_flock, "inode", "i_flock");
        MEMBER_OFFSET_INIT(file_lock_fl_owner, "file_lock", "fl_owner");
        MEMBER_OFFSET_INIT(nlm_host_h_exportent, "nlm_host", "h_exportent");
        MEMBER_OFFSET_INIT(svc_client_cl_ident, "svc_client", "cl_ident");
	MEMBER_OFFSET_INIT(inode_i_fop, "inode","i_fop");

	STRUCT_SIZE_INIT(umode_t, "umode_t");
	STRUCT_SIZE_INIT(dentry, "dentry");
	STRUCT_SIZE_INIT(files_struct, "files_struct");
	if (VALID_MEMBER(files_struct_fdt))
		STRUCT_SIZE_INIT(fdtable, "fdtable");
	STRUCT_SIZE_INIT(file, "file");
	STRUCT_SIZE_INIT(inode, "inode");
	STRUCT_SIZE_INIT(vfsmount, "vfsmount");
	STRUCT_SIZE_INIT(fs_struct, "fs_struct");
	STRUCT_SIZE_INIT(super_block, "super_block");

	if (!(ft->file_cache = (char *)malloc(SIZE(file)*FILE_CACHE)))
		error(FATAL, "cannot malloc file cache\n");
	if (!(ft->dentry_cache = (char *)malloc(SIZE(dentry)*DENTRY_CACHE)))
		error(FATAL, "cannot malloc dentry cache\n");
	if (!(ft->inode_cache = (char *)malloc(SIZE(inode)*INODE_CACHE)))
		error(FATAL, "cannot malloc inode cache\n");

	if (symbol_exists("height_to_maxindex")) {
		int tmp;
		ARRAY_LENGTH_INIT(tmp, height_to_maxindex,
                        "height_to_maxindex", NULL, 0);
		STRUCT_SIZE_INIT(radix_tree_root, "radix_tree_root");
		STRUCT_SIZE_INIT(radix_tree_node, "radix_tree_node");
		MEMBER_OFFSET_INIT(radix_tree_root_height, 
			"radix_tree_root","height");
		MEMBER_OFFSET_INIT(radix_tree_root_rnode, 
			"radix_tree_root","rnode");
	}
}

void
dump_filesys_table(int verbose)
{
	int i;
	ulong fhits, dhits, ihits;

	if (!verbose)
		goto show_hit_rates;

        for (i = 0; i < FILE_CACHE; i++)
                fprintf(stderr, "   cached_file[%2d]: %lx (%ld)\n",
                        i, ft->cached_file[i],
                        ft->cached_file_hits[i]);
        fprintf(stderr, "        file_cache: %lx\n", (ulong)ft->file_cache);
        fprintf(stderr, "  file_cache_index: %d\n", ft->file_cache_index);
        fprintf(stderr, "  file_cache_fills: %ld\n", ft->file_cache_fills);

	for (i = 0; i < DENTRY_CACHE; i++)
		fprintf(stderr, "  cached_dentry[%2d]: %lx (%ld)\n", 
			i, ft->cached_dentry[i],
			ft->cached_dentry_hits[i]);
	fprintf(stderr, "      dentry_cache: %lx\n", (ulong)ft->dentry_cache);
	fprintf(stderr, "dentry_cache_index: %d\n", ft->dentry_cache_index);
	fprintf(stderr, "dentry_cache_fills: %ld\n", ft->dentry_cache_fills);

        for (i = 0; i < INODE_CACHE; i++)
                fprintf(stderr, "  cached_inode[%2d]: %lx (%ld)\n",
                        i, ft->cached_inode[i],
                        ft->cached_inode_hits[i]);
        fprintf(stderr, "       inode_cache: %lx\n", (ulong)ft->inode_cache);
        fprintf(stderr, " inode_cache_index: %d\n", ft->inode_cache_index);
        fprintf(stderr, " inode_cache_fills: %ld\n", ft->inode_cache_fills);

show_hit_rates:
        if (ft->file_cache_fills) {
                for (i = fhits = 0; i < FILE_CACHE; i++)
                        fhits += ft->cached_file_hits[i];

                fprintf(stderr, "     file hit rate: %2ld%% (%ld of %ld)\n",
                        (fhits * 100)/ft->file_cache_fills,
                        fhits, ft->file_cache_fills);
	} 

        if (ft->dentry_cache_fills) {
                for (i = dhits = 0; i < DENTRY_CACHE; i++)
                        dhits += ft->cached_dentry_hits[i];

		fprintf(stderr, "   dentry hit rate: %2ld%% (%ld of %ld)\n",
			(dhits * 100)/ft->dentry_cache_fills,
			dhits, ft->dentry_cache_fills);
	}

        if (ft->inode_cache_fills) {
                for (i = ihits = 0; i < INODE_CACHE; i++)
                        ihits += ft->cached_inode_hits[i];

		fprintf(stderr, "    inode hit rate: %2ld%% (%ld of %ld)\n",
                        (ihits * 100)/ft->inode_cache_fills,
                        ihits, ft->inode_cache_fills);
	}
}

/*
 *  This command displays information about the open files of a context.
 *  For each open file descriptor the file descriptor number, a pointer
 *  to the file struct, pointer to the dentry struct, pointer to the inode 
 *  struct, indication of file type and pathname are printed.
 *  The argument can be a task address or a PID number; if no args, the 
 *  current context is used.
 *  If the flag -l is passed, any files held open in the kernel by the
 *  lockd server on behalf of an NFS client are displayed.
 */

#define FILES_LOCKD 1

void
cmd_files(void)
{
	int c;
	ulong flag;
	ulong value;
	struct task_context *tc;
	int subsequent;
	struct reference reference, *ref;
	char *refarg;

        ref = NULL;
	flag = 0;

        while ((c = getopt(argcnt, args, "d:lR:")) != EOF) {
                switch(c)
		{
		case 'l':
			flag |= FILES_LOCKD;
			break;

		case 'R':
			if (ref) {
				error(INFO, "only one -R option allowed\n");
				argerrs++;
			} else {
				ref = &reference;
        			BZERO(ref, sizeof(struct reference));
				ref->str = refarg = optarg;
			}
			break;

		case 'd':
			value = htol(optarg, FAULT_ON_ERROR, NULL);
			display_dentry_info(value);
			return;

		default:
			argerrs++;
			break;
		}
	}

	if ((flag & FILES_LOCKD) && ref) {
		error(INFO, "-R option not applicable to -l option\n");
		argerrs++;
	}

	if (argerrs)
		cmd_usage(pc->curcmd, SYNOPSIS);

	if (!args[optind]) {
		if (flag & FILES_LOCKD) {
			nlm_files_dump();
		} else {
			if (!ref)
				print_task_header(fp, CURRENT_CONTEXT(), 0);
			open_files_dump(CURRENT_TASK(), 0, ref);
		}
		return;
	}

	subsequent = 0;

	while (args[optind]) {

		if (ref && subsequent) {
                        BZERO(ref, sizeof(struct reference));
                        ref->str = refarg;
                }

                switch (str_to_context(args[optind], &value, &tc))
                {
                case STR_PID:
                        for (tc = pid_to_context(value); tc; tc = tc->tc_next) {
                                if (!ref)
                                        print_task_header(fp, tc, subsequent);
                                open_files_dump(tc->task, 0, ref);
                                fprintf(fp, "\n");
                        }
                        break;

                case STR_TASK:
                        if (!ref)
                                print_task_header(fp, tc, subsequent);
                        open_files_dump(tc->task, 0, ref);
                        break;

                case STR_INVALID:
                        error(INFO, "invalid task or pid value: %s\n",
                                args[optind]);
                        break;
                }

		subsequent++;
		optind++;
	}

	if (flag & FILES_LOCKD) {
		fprintf(fp, "\n");
		nlm_files_dump();
	}
}

#define FILES_REF_HEXNUM (0x1)
#define FILES_REF_DECNUM (0x2)
#define FILES_REF_FOUND  (0x4)

#define PRINT_FILE_REFERENCE()                  \
	if (!root_pwd_printed) {                \
        	print_task_header(fp, tc, 0);   \
                fprintf(fp, root_pwd);          \
		root_pwd_printed = TRUE;        \
	}                                       \
	if (!header_printed) {                  \
		fprintf(fp, files_header);      \
                header_printed = TRUE;          \
	}                                       \
	fprintf(fp, buf4);                      \
	ref->cmdflags |= FILES_REF_FOUND;

#define FILENAME_COMPONENT(P,C) \
        ((STREQ((P), "/") && STREQ((C), "/")) || \
	(!STREQ((C), "/") && strstr((P),(C))))  



/*
 *  open_files_dump() does the work for cmd_files().
 */

void
open_files_dump(ulong task, int flags, struct reference *ref)
{
        struct task_context *tc;
	ulong files_struct_addr; 
	ulong fdtable_addr = 0;
	char *files_struct_buf, *fdtable_buf = NULL;
	ulong fs_struct_addr;
	char *dentry_buf, *fs_struct_buf;
	ulong root_dentry, pwd_dentry;
	ulong root_inode, pwd_inode;
	ulong vfsmnt;
	int max_fdset = 0;
	int max_fds = 0;
	ulong open_fds_addr;
	fd_set open_fds;
	ulong fd;
	ulong file;
	ulong value;
	int i, j;
	int header_printed = 0;
	char root_pathname[BUFSIZE];
	char pwd_pathname[BUFSIZE];
	char files_header[BUFSIZE];
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char buf3[BUFSIZE];
	char buf4[BUFSIZE];
	char root_pwd[BUFSIZE];
	int root_pwd_printed = 0;

	BZERO(root_pathname, BUFSIZE);
	BZERO(pwd_pathname, BUFSIZE);
	files_struct_buf = GETBUF(SIZE(files_struct));
	if (VALID_STRUCT(fdtable))
		fdtable_buf = GETBUF(SIZE(fdtable));
	fill_task_struct(task);

	sprintf(files_header, " FD%s%s%s%s%s%s%sTYPE%sPATH\n",
		space(MINSPACE),
		mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "FILE"),
		space(MINSPACE),
		mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "DENTRY"),
		space(MINSPACE),
		mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "INODE"),
		space(MINSPACE),
		space(MINSPACE));

	tc = task_to_context(task);

	if (ref) 
		ref->cmdflags = 0;

	fs_struct_addr = ULONG(tt->task_struct + OFFSET(task_struct_fs));

        if (fs_struct_addr) {
		fs_struct_buf = GETBUF(SIZE(fs_struct));
                readmem(fs_struct_addr, KVADDR, fs_struct_buf, SIZE(fs_struct), 
			"fs_struct buffer", FAULT_ON_ERROR);

		root_dentry = ULONG(fs_struct_buf + OFFSET(fs_struct_root));

		if (root_dentry) {
			if (VALID_MEMBER(fs_struct_rootmnt)) {
                		vfsmnt = ULONG(fs_struct_buf +
                        		OFFSET(fs_struct_rootmnt));
				get_pathname(root_dentry, root_pathname, 
					BUFSIZE, 1, vfsmnt);
			} else {
				get_pathname(root_dentry, root_pathname, 
					BUFSIZE, 1, 0);
			}
		}

		pwd_dentry = ULONG(fs_struct_buf + OFFSET(fs_struct_pwd));

		if (pwd_dentry) {
			if (VALID_MEMBER(fs_struct_pwdmnt)) {
                		vfsmnt = ULONG(fs_struct_buf +
                        		OFFSET(fs_struct_pwdmnt));
				get_pathname(pwd_dentry, pwd_pathname, 
					BUFSIZE, 1, vfsmnt);
			} else {
				get_pathname(pwd_dentry, pwd_pathname, 
					BUFSIZE, 1, 0);
			}
		}

		if ((flags & PRINT_INODES) && root_dentry && pwd_dentry) {
			dentry_buf = fill_dentry_cache(root_dentry);
			root_inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));
			dentry_buf = fill_dentry_cache(pwd_dentry);
			pwd_inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));
			fprintf(fp, "ROOT: %lx %s    CWD: %lx %s\n", 
				root_inode, root_pathname, pwd_inode,
				pwd_pathname);
		} else if (ref) {
			snprintf(root_pwd, sizeof(root_pwd),
			     	"ROOT: %s    CWD: %s \n", 
				root_pathname, pwd_pathname);
			if (FILENAME_COMPONENT(root_pathname, ref->str) ||
			    FILENAME_COMPONENT(pwd_pathname, ref->str)) {
				print_task_header(fp, tc, 0);
				fprintf(fp, "%s", root_pwd); 
				root_pwd_printed = TRUE;
				ref->cmdflags |= FILES_REF_FOUND;
			}
		} else
			fprintf(fp, "ROOT: %s    CWD: %s\n", 
				root_pathname, pwd_pathname);

		FREEBUF(fs_struct_buf);
	}

	files_struct_addr = ULONG(tt->task_struct + OFFSET(task_struct_files));

	if (files_struct_addr) {
		readmem(files_struct_addr, KVADDR, files_struct_buf,
			SIZE(files_struct), "files_struct buffer",
			FAULT_ON_ERROR);
	
		if (VALID_MEMBER(files_struct_max_fdset)) {
			max_fdset = INT(files_struct_buf +
			OFFSET(files_struct_max_fdset));

			max_fds = INT(files_struct_buf +
			OFFSET(files_struct_max_fds));
		}
	}

	if (VALID_MEMBER(files_struct_fdt)) {
		fdtable_addr = ULONG(files_struct_buf + OFFSET(files_struct_fdt));

		if (fdtable_addr) {
			readmem(fdtable_addr, KVADDR, fdtable_buf,
	 			SIZE(fdtable), "fdtable buffer", FAULT_ON_ERROR); 
			max_fdset = INT(fdtable_buf +
				OFFSET(fdtable_max_fdset));
			max_fds = INT(fdtable_buf +
        	                OFFSET(fdtable_max_fds));
		}
	}

	if ((VALID_MEMBER(files_struct_fdt) && !fdtable_addr) || 
	    !files_struct_addr || max_fdset == 0 || max_fds == 0) {
		if (ref) {
			if (ref->cmdflags & FILES_REF_FOUND)
				fprintf(fp, "\n");
		} else
			fprintf(fp, "No open files\n");
		if (fdtable_buf)
			FREEBUF(fdtable_buf);
		FREEBUF(files_struct_buf);
		return;
	}

        if (ref && IS_A_NUMBER(ref->str)) { 
                if (hexadecimal_only(ref->str, 0)) {
                        ref->hexval = htol(ref->str, FAULT_ON_ERROR, NULL);
                        ref->cmdflags |= FILES_REF_HEXNUM;
                } else {
			value = dtol(ref->str, FAULT_ON_ERROR, NULL);
			if (value <= MAX(max_fdset, max_fds)) {
                              	ref->decval = value;
                               	ref->cmdflags |= FILES_REF_DECNUM;
			} else {
                             	ref->hexval = htol(ref->str, 
					FAULT_ON_ERROR, NULL);
                                ref->cmdflags |= FILES_REF_HEXNUM;
			}
		}
        }

	if (VALID_MEMBER(fdtable_open_fds))
		open_fds_addr = ULONG(fdtable_buf +
			OFFSET(fdtable_open_fds));
	else
		open_fds_addr = ULONG(files_struct_buf +
			OFFSET(files_struct_open_fds));

	if (open_fds_addr) {
		if (VALID_MEMBER(files_struct_open_fds_init) && 
		    (open_fds_addr == (files_struct_addr + 
		    OFFSET(files_struct_open_fds_init)))) 
			BCOPY(files_struct_buf + 
			        OFFSET(files_struct_open_fds_init),
				&open_fds, sizeof(fd_set));
		else
			readmem(open_fds_addr, KVADDR, &open_fds,
				sizeof(fd_set), "fdtable open_fds",
				FAULT_ON_ERROR);
	} 

	if (VALID_MEMBER(fdtable_fd))
		fd = ULONG(fdtable_buf + OFFSET(fdtable_fd));
	else
		fd = ULONG(files_struct_buf + OFFSET(files_struct_fd));

	if (!open_fds_addr || !fd) {
                if (ref && (ref->cmdflags & FILES_REF_FOUND))
                	fprintf(fp, "\n");
		if (fdtable_buf)
			FREEBUF(fdtable_buf);
		FREEBUF(files_struct_buf);
		return;
	}

	j = 0;
	for (;;) {
		unsigned long set;
		i = j * __NFDBITS;
		if (i >= max_fdset || i >= max_fds)
			 break;
		set = open_fds.__fds_bits[j++];
		while (set) {
			if (set & 1) {
        			readmem(fd + i*sizeof(struct file *), KVADDR, 
					&file, sizeof(struct file *), 
					"fd file", FAULT_ON_ERROR);

				if (ref && file) {
					open_tmpfile();
                                        if (file_dump(file, 0, 0, i,
                                            DUMP_FULL_NAME)) {
						BZERO(buf4, BUFSIZE);
						rewind(pc->tmpfile);
						fgets(buf4, BUFSIZE, 
							pc->tmpfile);
						close_tmpfile();
						ref->refp = buf4;
						if (open_file_reference(ref)) { 
							PRINT_FILE_REFERENCE();
						}
					} else
						close_tmpfile();
				}
				else if (file) {
					if (!header_printed) {
						fprintf(fp, files_header);
						header_printed = 1;
					}
					file_dump(file, 0, 0, i,
						  DUMP_FULL_NAME);
				}
			}
			i++;
			set >>= 1;
		}
	}

	if (!header_printed && !ref)
		fprintf(fp, "No open files\n");

	if (ref && (ref->cmdflags & FILES_REF_FOUND))
		fprintf(fp, "\n");

	if (fdtable_buf)
		FREEBUF(fdtable_buf);
	FREEBUF(files_struct_buf);
}

/*
 *  Check an open file string for references.  
 */
static int
open_file_reference(struct reference *ref)
{
	char buf[BUFSIZE];
	char *arglist[MAXARGS];
	int i, fd, argcnt;
	ulong vaddr;

	strcpy(buf, ref->refp);
	if ((argcnt = parse_line(buf, arglist)) < 5)
		return FALSE;

	if (ref->cmdflags & (FILES_REF_HEXNUM|FILES_REF_DECNUM)) {
		fd = dtol(arglist[0], FAULT_ON_ERROR, NULL);
		if (((ref->cmdflags & FILES_REF_HEXNUM) && 
		    (fd == ref->hexval)) || 
                    ((ref->cmdflags & FILES_REF_DECNUM) &&
		    (fd == ref->decval))) {
			return TRUE;
		}

        	for (i = 1; i < 4; i++) {
        		vaddr = htol(arglist[i], FAULT_ON_ERROR, NULL);
        		if (vaddr == ref->hexval) 
        			return TRUE;
        	}
	}

	if (STREQ(ref->str, arglist[4])) {
		return TRUE;
	}

	if ((argcnt == 6) && FILENAME_COMPONENT(arglist[5], ref->str)) {
		return TRUE;
	}
	
	return FALSE;
}

/*
 * nlm_files_dump() prints files held open by lockd server on behalf
 * of NFS clients
 */

#define FILE_NRHASH 32

char nlm_files_header[BUFSIZE] = { 0 };
char *nlm_header = \
"Files open by lockd for client discretionary file locks:\n";

void
nlm_files_dump(void)
{
	int header_printed = 0;
	int i, j, cnt;
	ulong nlmsvc_ops, nlm_files;
	struct syment *nsp;
	ulong nlm_files_array[FILE_NRHASH];
	struct list_data list_data, *ld;
	ulong *file, *files_list;
	ulong dentry, inode, flock, host, client;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];

        if (!strlen(nlm_files_header)) {
                sprintf(nlm_files_header,
                    "CLIENT               %s %s%sTYPE%sPATH\n",
                        mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "NLM_FILE"),
                        mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "INODE"),
                        space(MINSPACE),
                        space(MINSPACE));
        }

	if (!symbol_exists("nlm_files") || !symbol_exists("nlmsvc_ops")
	    || !symbol_exists("nfsd_nlm_ops")) {
		goto out;
	}
	get_symbol_data("nlmsvc_ops", sizeof(void *), &nlmsvc_ops);
	if (nlmsvc_ops != symbol_value("nfsd_nlm_ops")) {
		goto out;
	}
	if ((nsp = next_symbol("nlm_files", NULL)) == NULL) {
		error(WARNING, "cannot find next symbol after nlm_files\n");
		goto out;
	}
	nlm_files = symbol_value("nlm_files");
	if (((nsp->value - nlm_files) / sizeof(void *)) != FILE_NRHASH ) {
		error(WARNING, "FILE_NRHASH has changed from %d\n", 
		      FILE_NRHASH);
		if (((nsp->value - nlm_files) / sizeof(void *)) < 
		    FILE_NRHASH )
			goto out;
	}

	readmem(nlm_files, KVADDR, nlm_files_array, 
		sizeof(ulong) * FILE_NRHASH, "nlm_files array",
		FAULT_ON_ERROR);
	for (i = 0; i < FILE_NRHASH; i++) {
		if (nlm_files_array[i] == 0) {
			continue;
		}
		ld = &list_data;
		BZERO(ld, sizeof(struct list_data));	
		ld->start = nlm_files_array[i];
		hq_open();
		cnt = do_list(ld);
		files_list = (ulong *)GETBUF(cnt * sizeof(ulong));
		cnt = retrieve_list(files_list, cnt);
		hq_close();
		for (j=0, file = files_list; j<cnt; j++, file++) {
			readmem(*file + OFFSET(nlm_file_f_file) + 
				OFFSET(file_f_dentry), KVADDR, &dentry,
				sizeof(void *), "nlm_file dentry", 
				FAULT_ON_ERROR);
			if (!dentry)
				continue;
			readmem(dentry + OFFSET(dentry_d_inode), KVADDR, 
				&inode, sizeof(void *), "dentry d_inode",
				FAULT_ON_ERROR);
			if (!inode)
				continue;
			readmem(inode + OFFSET(inode_i_flock), KVADDR,
				&flock, sizeof(void *), "inode i_flock",
				FAULT_ON_ERROR);
			if (!flock)
				continue;
			readmem(flock + OFFSET(file_lock_fl_owner), KVADDR,
				&host, sizeof(void *), 
				"file_lock fl_owner", FAULT_ON_ERROR);
			if (!host)
				continue;
			readmem(host + OFFSET(nlm_host_h_exportent), KVADDR,
				&client, sizeof(void *), 
				"nlm_host h_exportent", FAULT_ON_ERROR);
			if (!client)
				continue;
			if (!read_string(client + OFFSET(svc_client_cl_ident), 
			    buf1, BUFSIZE-1))
				continue;
			if (!header_printed) {
				fprintf(fp, nlm_header);
				fprintf(fp, nlm_files_header);
				header_printed = 1;
			}

			fprintf(fp, "%-20s %8lx ", buf1, *file);
			file_dump(*file, dentry, inode, 0, 
				  DUMP_INODE_ONLY | DUMP_FULL_NAME);
		}
	}
out:
	if (!header_printed)
		fprintf(fp, "No lockd server files open for NFS clients\n");
}
	    
/*
 * file_dump() prints info for an open file descriptor
 */

static int
file_dump(ulong file, ulong dentry, ulong inode, int fd, int flags)
{
	ulong vfsmnt;
	char *dentry_buf, *file_buf, *inode_buf, *type;
	char pathname[BUFSIZE];
	char *printpath;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char buf3[BUFSIZE];

	if (!dentry && file) {
		file_buf = fill_file_cache(file);		
		dentry = ULONG(file_buf + OFFSET(file_f_dentry));
	}

	if (!dentry) 
		return FALSE;

	if (!inode) {
		dentry_buf = fill_dentry_cache(dentry);
		inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));
	}

	if (!inode) 
		return FALSE;

	inode_buf = fill_inode_cache(inode);

	if (flags & DUMP_FULL_NAME) {
		if (VALID_MEMBER(file_f_vfsmnt)) {
			vfsmnt = ULONG(file_buf + OFFSET(file_f_vfsmnt));
			get_pathname(dentry, pathname, BUFSIZE, 1, vfsmnt);
		} else {
			get_pathname(dentry, pathname, BUFSIZE, 1, 0);
		}
	} else
		get_pathname(dentry, pathname, BUFSIZE, 0, 0);

	type = inode_type(inode_buf, pathname);

	if (flags & DUMP_FULL_NAME)
		printpath = pathname;
	else
		printpath = pathname+1;

	if (flags & DUMP_INODE_ONLY) {
		fprintf(fp, "%s%s%s%s%s\n",
			mkstring(buf1, VADDR_PRLEN, 
			CENTER|RJUST|LONG_HEX, 
			MKSTR(inode)),
			space(MINSPACE),
			type, 
			space(MINSPACE),
			printpath);
	} else {
		if (flags & DUMP_DENTRY_ONLY) {
			fprintf(fp, "%s%s%s%s%s%s%s\n",
				mkstring(buf1, VADDR_PRLEN, 
				CENTER|RJUST|LONG_HEX, 
				MKSTR(dentry)),
				space(MINSPACE),
				mkstring(buf2, VADDR_PRLEN, 
				CENTER|RJUST|LONG_HEX, 
				MKSTR(inode)),
				space(MINSPACE),
				type, 
				space(MINSPACE),
				pathname+1);
		} else {
                        fprintf(fp, "%3d%s%s%s%s%s%s%s%s%s%s\n",
                                fd,
                                space(MINSPACE),
				mkstring(buf1, VADDR_PRLEN, 
				CENTER|RJUST|LONG_HEX, 
				MKSTR(file)),
                                space(MINSPACE),
				mkstring(buf2, VADDR_PRLEN, 
				CENTER|RJUST|LONG_HEX, 
				MKSTR(dentry)),
                                space(MINSPACE),
				mkstring(buf3, VADDR_PRLEN, 
				CENTER|RJUST|LONG_HEX, 
				MKSTR(inode)),
                                space(MINSPACE),
                                type,
                                space(MINSPACE),
                                pathname);
		}
	}

	return TRUE;
}

/*
 *  Get the dentry associated with a file.
 */
ulong
file_to_dentry(ulong file)
{
        char *file_buf;
	ulong dentry;

        file_buf = fill_file_cache(file);
        dentry = ULONG(file_buf + OFFSET(file_f_dentry));
        return dentry;
}

/*
 *  Get the vfsmnt associated with a file.
 */
ulong
file_to_vfsmnt(ulong file)
{
	char *file_buf;
	ulong vfsmnt;

	file_buf = fill_file_cache(file);
	vfsmnt = ULONG(file_buf + OFFSET(file_f_vfsmnt));
	return vfsmnt;
}

/*
 * get_pathname() fills in a pathname string for an ending dentry
 * See __d_path() in the kernel for help fixing problems.
 */
void
get_pathname(ulong dentry, char *pathname, int length, int full, ulong vfsmnt)
{
	char buf[BUFSIZE];
	char tmpname[BUFSIZE];
	ulong tmp_dentry, parent;
	int d_name_len = 0;
	ulong d_name_name;
	ulong tmp_vfsmnt, mnt_parent;
	char *dentry_buf, *vfsmnt_buf;

	BZERO(buf, BUFSIZE);
	BZERO(tmpname, BUFSIZE);
	BZERO(pathname, length);
	vfsmnt_buf = VALID_MEMBER(vfsmount_mnt_mountpoint) ? 
		GETBUF(SIZE(vfsmount)) : NULL;

	parent = dentry;
	tmp_vfsmnt = vfsmnt;

	do {
		tmp_dentry = parent;

		dentry_buf = fill_dentry_cache(tmp_dentry);

		d_name_len = INT(dentry_buf +
			OFFSET(dentry_d_name) + OFFSET(qstr_len));

		if (!d_name_len) 
			break;

		d_name_name = ULONG(dentry_buf + OFFSET(dentry_d_name) 
			+ OFFSET(qstr_name));

		if (!d_name_name)
			break;

		if (!get_pathname_component(tmp_dentry, d_name_name, d_name_len,
		     dentry_buf, buf))
			break;

		if (tmp_dentry != dentry) {
			strncpy(tmpname, pathname, BUFSIZE);
			if (strlen(tmpname) + d_name_len < BUFSIZE) {
				if ((d_name_len > 1 || !STREQ(buf, "/")) &&
				    !STRNEQ(tmpname, "/")) {
					sprintf(pathname, "%s%s%s", buf, 
						"/", tmpname);
				} else {
					sprintf(pathname, 
						"%s%s", buf, tmpname);
				}
			}
		} else {
			strncpy(pathname, buf, BUFSIZE);
		}

		parent = ULONG(dentry_buf + OFFSET(dentry_d_parent)); 
			
		if (tmp_dentry == parent && full) {
			if (VALID_MEMBER(vfsmount_mnt_mountpoint)) {
				if (tmp_vfsmnt) {
					if (strncmp(pathname, "//", 2) == 0)
						shift_string_left(pathname, 1);
                                        readmem(tmp_vfsmnt, KVADDR, vfsmnt_buf,
						SIZE(vfsmount), 
						"vfsmount buffer", 
						FAULT_ON_ERROR);
        				parent = ULONG(vfsmnt_buf + 
					    OFFSET(vfsmount_mnt_mountpoint));
        				mnt_parent = ULONG(vfsmnt_buf + 
					    OFFSET(vfsmount_mnt_parent));
					if (tmp_vfsmnt == mnt_parent)
						break;
					else
						tmp_vfsmnt = mnt_parent;
				}
			} else {
				parent = ULONG(dentry_buf + 
					OFFSET(dentry_d_covers)); 
			}
		}
						
	} while (tmp_dentry != parent && parent);

	if (vfsmnt_buf)
		FREEBUF(vfsmnt_buf);
}

/*
 *  If the pathname component, which may be internal or external to the 
 *  dentry, has string length equal to what's expected, copy it into the
 *  passed-in buffer, and return its length.  If it doesn't match, return 0.
 */
static int
get_pathname_component(ulong dentry, 
		       ulong d_name_name,
		       int d_name_len,
		       char *dentry_buf, 
		       char *pathbuf)
{
	int len = d_name_len;   /* presume success */

        if (d_name_name == (dentry + OFFSET(dentry_d_iname))) {
                if (strlen(dentry_buf + OFFSET(dentry_d_iname)) == d_name_len)
                	strcpy(pathbuf, dentry_buf + OFFSET(dentry_d_iname));
                else
                        len = 0;
        } else if ((read_string(d_name_name, pathbuf, BUFSIZE)) != d_name_len)
                len = 0;

	return d_name_len;
}

/*
 *  Cache the passed-in file structure.
 */
char *
fill_file_cache(ulong file)
{
        int i;
        char *cache;

        ft->file_cache_fills++;

        for (i = 0; i < DENTRY_CACHE; i++) {
                if (ft->cached_file[i] == file) {
                        ft->cached_file_hits[i]++;
                        cache = ft->file_cache + (SIZE(file)*i);
                        return(cache);
                }
        }

        cache = ft->file_cache + (SIZE(file)*ft->file_cache_index);

        readmem(file, KVADDR, cache, SIZE(file),
                "fill_file_cache", FAULT_ON_ERROR);

        ft->cached_file[ft->file_cache_index] = file;

        ft->file_cache_index = (ft->file_cache_index+1) % DENTRY_CACHE;

        return(cache);
}

/*
 *  If active, clear the file references.
 */
void
clear_file_cache(void)
{
        int i;

        if (DUMPFILE())
                return;

        for (i = 0; i < DENTRY_CACHE; i++) {
                ft->cached_file[i] = 0;
                ft->cached_file_hits[i] = 0;
        }

        ft->file_cache_fills = 0;
        ft->file_cache_index = 0;
}



/*
 *  Cache the passed-in dentry structure.
 */
char *
fill_dentry_cache(ulong dentry)
{
	int i;
	char *cache;

	ft->dentry_cache_fills++;

        for (i = 0; i < DENTRY_CACHE; i++) {
                if (ft->cached_dentry[i] == dentry) {
			ft->cached_dentry_hits[i]++;
			cache = ft->dentry_cache + (SIZE(dentry)*i);
			return(cache);
		}
	}

	cache = ft->dentry_cache + (SIZE(dentry)*ft->dentry_cache_index);

        readmem(dentry, KVADDR, cache, SIZE(dentry),
        	"fill_dentry_cache", FAULT_ON_ERROR);

	ft->cached_dentry[ft->dentry_cache_index] = dentry;

	ft->dentry_cache_index = (ft->dentry_cache_index+1) % DENTRY_CACHE;

	return(cache);
}

/*
 *  If active, clear the dentry references.
 */
void
clear_dentry_cache(void)
{
	int i;

	if (DUMPFILE())
		return;

        for (i = 0; i < DENTRY_CACHE; i++) {
                ft->cached_dentry[i] = 0;
        	ft->cached_dentry_hits[i] = 0;
	}

        ft->dentry_cache_fills = 0;
	ft->dentry_cache_index = 0;
}

/*
 *  Cache the passed-in inode structure.
 */
char *
fill_inode_cache(ulong inode)
{
        int i;
        char *cache;

        ft->inode_cache_fills++;

        for (i = 0; i < INODE_CACHE; i++) {
                if (ft->cached_inode[i] == inode) {
                        ft->cached_inode_hits[i]++;
                        cache = ft->inode_cache + (SIZE(inode)*i);
                        return(cache);
                }
        }

        cache = ft->inode_cache + (SIZE(inode)*ft->inode_cache_index);

        readmem(inode, KVADDR, cache, SIZE(inode),
                "fill_inode_cache", FAULT_ON_ERROR);

        ft->cached_inode[ft->inode_cache_index] = inode;

        ft->inode_cache_index = (ft->inode_cache_index+1) % INODE_CACHE;

        return(cache);
}

/*      
 *  If active, clear the inode references.
 */
void
clear_inode_cache(void)
{
        int i; 
 
        if (DUMPFILE())
                return;
 
        for (i = 0; i < DENTRY_CACHE; i++) {
                ft->cached_inode[i] = 0;
                ft->cached_inode_hits[i] = 0;
        }

        ft->inode_cache_fills = 0;
        ft->inode_cache_index = 0;
}


/*
 *  This command displays the tasks using specified files or sockets.
 *  Tasks will be listed that reference the file as the current working
 *  directory, root directory, an open file descriptor, or that mmap the
 *  file.
 *  The argument can be a full pathname without symbolic links, or inode 
 *  address.
 */

void
cmd_fuser(void)
{
	int c;
	int subsequent;
	char *spec_string, *tmp;
	struct foreach_data foreach_data, *fd;
	char task_buf[BUFSIZE];
	char buf[BUFSIZE];
	char uses[20];
	char client[20];
	char fuser_header[BUFSIZE];
	int doing_fds, doing_mmap, doing_lockd, len;
	int fuser_header_printed, lockd_header_printed;

        while ((c = getopt(argcnt, args, "")) != EOF) {
                switch(c)
		{
		default:
			argerrs++;
			break;
		}
	}

	if (argerrs)
		cmd_usage(pc->curcmd, SYNOPSIS);

	if (!args[optind]) {
		cmd_usage(pc->curcmd, SYNOPSIS);
		return;
	}

	sprintf(fuser_header, " PID   %s  COMM             USAGE\n",
		mkstring(buf, VADDR_PRLEN, CENTER, "TASK"));

	subsequent = 0;
	while (args[optind]) {
                spec_string = args[optind];
		if (STRNEQ(spec_string, "0x") && hexadecimal(spec_string, 0))
			shift_string_left(spec_string, 2);
		len = strlen(spec_string);
		fuser_header_printed = 0;
		lockd_header_printed = 0;
		open_tmpfile();
		BZERO(&foreach_data, sizeof(struct foreach_data));
		fd = &foreach_data;
		fd->keyword_array[0] = FOREACH_FILES;
		fd->keyword_array[1] = FOREACH_VM;
		fd->keys = 2;
		fd->flags |= FOREACH_i_FLAG;
		fd->flags |= FOREACH_l_FLAG;
		foreach(fd);
		rewind(pc->tmpfile);
		BZERO(uses, 20);
		while (fgets(buf, BUFSIZE, pc->tmpfile)) {
			if (STRNEQ(buf, "PID:")) {
				if (!STREQ(uses, "")) {
					if (!fuser_header_printed) {
						fprintf(pc->saved_fp,
							fuser_header);
						fuser_header_printed = 1;
					}
					show_fuser(task_buf, uses);
					BZERO(uses, 20);
				}
				BZERO(task_buf, BUFSIZE);
				strcpy(task_buf, buf);
				doing_lockd = doing_fds = doing_mmap = 0;
				continue;
			}
			if (STRNEQ(buf, "ROOT:")) {
				if ((tmp = strstr(buf, spec_string)) &&
				    (tmp[len] == ' ' || tmp[len] == '\n')) {
					if (strstr(tmp, "CWD:")) {
						strcat(uses, "root ");
						if ((tmp = strstr(tmp+len,
						    spec_string)) &&
						    (tmp[len] == ' ' || 
						     tmp[len] == '\n')) {
							strcat(uses, "cwd ");
						}
					} else {
						strcat(uses, "cwd ");
					}
				}
				continue;
			}
			if (strstr(buf, "DENTRY")) {
				doing_fds = 1;
				continue;
			}
			if (strstr(buf, "TOTAL_VM")) {
				doing_fds = 0;
				continue;
			}
			if (strstr(buf, " VMA ")) {
				doing_mmap = 1;
				doing_fds = 0;
				continue;
			}
			if (STREQ(buf, nlm_header) ||
			    STREQ(buf, nlm_files_header)) {
				doing_lockd = 1;
				doing_fds = doing_mmap = 0;
				continue;
			}
			if ((tmp = strstr(buf, spec_string)) &&
			    (tmp[len] == ' ' || tmp[len] == '\n')) {
				if (doing_fds) {
					strcat(uses, "fd ");
					doing_fds = 0;
				}
				if (doing_mmap) {
					strcat(uses, "mmap ");
					doing_mmap = 0;
				}
				if (doing_lockd) {
					if (!lockd_header_printed) {
						fprintf(pc->saved_fp,
							"LOCKD CLIENTS:\n");
						lockd_header_printed = 1;
					}
					BZERO(client, 20);
					memccpy(client, buf, ' ', 20);
					fprintf(pc->saved_fp, "%s\n", client);
				}
			}

		}
		if (!STREQ(uses, "")) {
			if (!fuser_header_printed) {
				fprintf(pc->saved_fp, fuser_header);
				fuser_header_printed = 1;
			}
			show_fuser(task_buf, uses);
			BZERO(uses, 20);
		}
		close_tmpfile();
		optind++;
		if (!fuser_header_printed && !lockd_header_printed) {
			fprintf(fp, "No users of %s found\n", spec_string);
		}
	}
}

static void
show_fuser(char *buf, char *uses)
{
	char pid[10];
	char task[20];
	char command[20];
	char *p;
	int i;

	BZERO(pid, 10);
	BZERO(task, 20);
	BZERO(command, 20);
	p = strstr(buf, "PID: ") + strlen("PID: ");
	i = 0;
	while (*p != ' ' && i < 10) {
		pid[i++] = *p++;
	}
	pid[i] = NULLCHAR;

	p = strstr(buf, "TASK: ") + strlen("TASK: ");
	while (*p == ' ')
		p++;
	i = 0;
	while (*p != ' ' && i < 20) {
		task[i++] = *p++;
	}
	task[i] = NULLCHAR;
	mkstring(task, VADDR_PRLEN, RJUST, task);

	p = strstr(buf, "COMMAND: ") + strlen("COMMAND: ");
	strncpy(command, p, 16);
	i = strlen(command) - 1;
	while (i < 16) {
		command[i++] = ' ';
	}
	command[16] = NULLCHAR;

        fprintf(pc->saved_fp, "%5s  %s  %s %s\n",
                pid, task, command, uses);
}


/*
 *  Gather some host memory/swap statistics, passing back whatever the
 *  caller requires.
 */

int
monitor_memory(long *freemem_pages, 
	       long *freeswap_pages, 
	       long *mem_usage,
	       long *swap_usage)
{
	FILE *mp;
	char buf[BUFSIZE];
        char *arglist[MAXARGS];
        int argc, params;
	ulong freemem, memtotal, freeswap, swaptotal;

	if (!file_exists("/proc/meminfo", NULL))
		return FALSE;

	if ((mp = fopen("/proc/meminfo", "r")) == NULL)
		return FALSE;

	params = 0;

	while (fgets(buf, BUFSIZE, mp)) {
		if (strstr(buf, "SwapFree")) {
			params++;
			argc = parse_line(buf, arglist);
			if (decimal(arglist[1], 0)) 
				freeswap = (atol(arglist[1]) * 1024)/PAGESIZE();
		}
		
		if (strstr(buf, "MemFree")) {
			params++;
                        argc = parse_line(buf, arglist);
                        if (decimal(arglist[1], 0)) 
                                freemem = (atol(arglist[1]) * 1024)/PAGESIZE();
                }

                if (strstr(buf, "MemTotal")) {
			params++;
                        argc = parse_line(buf, arglist);
                        if (decimal(arglist[1], 0))
                                memtotal = (atol(arglist[1]) * 1024)/PAGESIZE();
                }

                if (strstr(buf, "SwapTotal")) {
                        params++;
                        argc = parse_line(buf, arglist);
                        if (decimal(arglist[1], 0))
                               swaptotal = (atol(arglist[1]) * 1024)/PAGESIZE();
                }

	}

	fclose(mp);

	if (params != 4)
		return FALSE;

	if (freemem_pages)
		*freemem_pages = freemem;
	if (freeswap_pages)
        	*freeswap_pages = freeswap;
	if (mem_usage)
		*mem_usage = ((memtotal-freemem)*100) / memtotal; 
	if (swap_usage)
		*swap_usage = ((swaptotal-freeswap)*100) / swaptotal;

	return TRUE;
}

/*
 *  Determine whether two filenames reference the same file.
 */
int
same_file(char *f1, char *f2)
{
	struct stat stat1, stat2;

	if ((stat(f1, &stat1) != 0) || (stat(f2, &stat2) != 0))
		return FALSE;

	if ((stat1.st_dev == stat2.st_dev) &&
	    (stat1.st_ino == stat2.st_ino))
		return TRUE;

	return FALSE;
}


/*
 *  Determine which live memory source to use.
 */

#define MODPROBE_CMD "/sbin/modprobe -l --type drivers/char"

static void 
get_live_memory_source(void)
{
	FILE *pipe;
	char buf[BUFSIZE];
	char modname1[BUFSIZE];
	char modname2[BUFSIZE];
	char *name;
	int use_module;
	struct stat stat1, stat2;

	pc->flags |= DEVMEM;
	if (pc->live_memsrc)
		goto live_report;

	pc->live_memsrc = "/dev/mem";
	use_module = FALSE;

	if (file_exists("/dev/mem", &stat1) &&
	    file_exists(pc->memory_device, &stat2) &&
	    S_ISCHR(stat1.st_mode) && S_ISCHR(stat2.st_mode) &&
	    (stat1.st_rdev == stat2.st_rdev)) { 
		if (!STREQ(pc->memory_device, "/dev/mem"))
			error(INFO, "%s: same device as /dev/mem\n%s", 
				pc->memory_device, 
				pc->memory_module ? "" : "\n");
		if (pc->memory_module)
			error(INFO, "ignoring --memory_module %s request\n\n", 
				pc->memory_module);
	} else if (pc->memory_module && memory_driver_module_loaded(NULL)) {
		error(INFO, "using pre-loaded \"%s\" module\n\n", 
			pc->memory_module);
		pc->flags |= MODPRELOAD;
		use_module = TRUE;
	} else {
		pc->memory_module = MEMORY_DRIVER_MODULE;

        	if ((pipe = popen(MODPROBE_CMD, "r")) == NULL) {
			error(INFO, "%s: %s\n", MODPROBE_CMD, strerror(errno));
                	return;
		}

		sprintf(modname1, "%s.o", pc->memory_module);
                sprintf(modname2, "%s.ko", pc->memory_module);
	        while (fgets(buf, BUFSIZE, pipe)) {
			name = basename(strip_linefeeds(buf));
			if (STREQ(name, modname1) || STREQ(name, modname2)) {
				use_module = TRUE;
				break;
			}
		}

		pclose(pipe);
	}

	if (use_module) {
		pc->flags &= ~DEVMEM;
		pc->flags |= MEMMOD;
		pc->readmem = read_memory_device;
		pc->writemem = write_memory_device;
		pc->live_memsrc = pc->memory_device;
	}

live_report:
	if (CRASHDEBUG(1)) 
		fprintf(fp, "get_live_memory_source: %s\n", pc->live_memsrc);
}

/*
 *  Read /proc/modules to determine whether the crash driver module
 *  has been loaded.
 */
static int
memory_driver_module_loaded(int *count)
{
        FILE *modules;
        int argcnt, module_loaded;
        char *arglist[MAXARGS];
	char buf[BUFSIZE];

        if ((modules = fopen("/proc/modules", "r")) == NULL) {
                error(INFO, "/proc/modules: %s\n", strerror(errno));
                return FALSE;
        }

        module_loaded = FALSE;
        while (fgets(buf, BUFSIZE, modules)) {
		console("%s", buf);
                argcnt = parse_line(buf, arglist);
                if (argcnt < 3) 
                        continue;
                if (STREQ(arglist[0], pc->memory_module)) {
                        module_loaded = TRUE;
                        if (CRASHDEBUG(1))
                                fprintf(stderr, 
				    "\"%s\" module loaded: [%s][%s][%s]\n", 
					arglist[0], arglist[0],
					arglist[1], arglist[2]);
			if (count) 
				*count = atoi(arglist[2]);
                        break;
                }
        }

        fclose(modules);
	
	return module_loaded;
}

/*
 *  Insmod the memory driver module.
 */
static int
insmod_memory_driver_module(void)
{
        FILE *pipe;
	char buf[BUFSIZE];
	char command[BUFSIZE];

	sprintf(command, "/sbin/modprobe %s", pc->memory_module);
	if (CRASHDEBUG(1))
		fprintf(fp, "%s\n", command);

        if ((pipe = popen(command, "r")) == NULL) {
		error(INFO, "%s: %s", command, strerror(errno));
		return FALSE;
	}

        while (fgets(buf, BUFSIZE, pipe))
        	fprintf(fp, "%s\n", buf);
        pclose(pipe);

	if (!memory_driver_module_loaded(NULL)) {
		error(INFO, "cannot insmod \"%s\" module\n", pc->memory_module);
		return FALSE;
	}

	return TRUE;
}

/*
 *  Return the dev_t for the memory device driver.  The major number will
 *  be that of the kernel's misc driver; the minor is dynamically created
 *  when the module at inmod time, and found in /proc/misc.
 */
static int
get_memory_driver_dev(dev_t *devp)
{
	char buf[BUFSIZE];
        char *arglist[MAXARGS];
        int argcnt;
	FILE *misc;
	int minor;
	dev_t dev;

	dev = 0;

        if ((misc = fopen("/proc/misc", "r")) == NULL) { 
		error(INFO, "/proc/misc: %s", strerror(errno));
        } else {
        	while (fgets(buf, BUFSIZE, misc)) {
                	argcnt = parse_line(buf, arglist);
			if ((argcnt == 2) && 
			    STREQ(arglist[1], pc->memory_module)) {
				minor = atoi(arglist[0]);
				dev = makedev(MISC_MAJOR, minor);
				if (CRASHDEBUG(1))
					fprintf(fp, 
					    "/proc/misc: %s %s => %d/%d\n",
						arglist[0], arglist[1], 
						major(dev), minor(dev));
				break;
			}
		}
		fclose(misc);
	}

	if (!dev) {
		error(INFO, "cannot determine minor number of %s driver\n",
			pc->memory_module); 
		return FALSE;
	}

	*devp = dev;
	return TRUE;
}

/*
 *  Deal with the creation or verification of the memory device file:
 *
 *   1. If the device exists, and has the correct major/minor device numbers,
 *      nothing needs to be done.
 *   2. If the filename exists, but it's not a device file, has the wrong
 *      major/minor device numbers, or the wrong permissions, advise the
 *      user to delete it.
 *   3. Otherwise, create it.
 */
static int 
create_memory_device(dev_t dev)
{
	struct stat stat;

	if (file_exists(pc->live_memsrc, &stat)) {
		/*
		 *  It already exists -- just use it.
		 */
		if ((stat.st_mode == MEMORY_DRIVER_DEVICE_MODE) && 
		    (stat.st_rdev == dev))
			return TRUE;

		/*
		 *  Either it's not a device special file, or it's got
		 *  the wrong major/minor numbers, or the wrong permissions.
		 *  Unlink the file -- it shouldn't be there.
		 */
		if (!S_ISCHR(stat.st_mode)) 
			error(FATAL, 
			    "%s: not a character device -- please delete it!\n",
				pc->live_memsrc);
		else if (dev != stat.st_rdev) 
			error(FATAL, 
			    "%s: invalid device: %d/%d  -- please delete it!\n",
				pc->live_memsrc, major(stat.st_rdev), 
				minor(stat.st_rdev));
		else 
			unlink(pc->live_memsrc);
	} 

	/* 
	 *  Either it doesn't exist or it was just unlinked.
	 *  In either case, try to create it.
	 */
	if (mknod(pc->live_memsrc, MEMORY_DRIVER_DEVICE_MODE, dev)) {
		error(INFO, "%s: mknod: %s\n", pc->live_memsrc,
			strerror(errno));
		return FALSE;
	}

	return TRUE;
}

/*
 *  If we're here, the memory driver module is being requested:
 *
 *   1. If the module is not already loaded, insmod it.
 *   2. Determine the misc driver minor device number that it was assigned.
 *   3. Create (or verify) the device file.
 *   4. Then just open it.
 */ 

static int 
memory_driver_init(void)
{
	dev_t dev;

	if (!memory_driver_module_loaded(NULL)) {
	    	if (!insmod_memory_driver_module()) 
			return FALSE;
	}

	if (!get_memory_driver_dev(&dev)) 
		return FALSE;

	if (!create_memory_device(dev)) 
		return FALSE;

	if ((pc->mfd = open(pc->memory_device, O_RDONLY)) < 0) { 
		error(INFO, "%s: open: %s\n", pc->memory_device, 
			strerror(errno));
		return FALSE;
	}

	return TRUE;
}

/*
 *  Remove the memory driver module and associated file.
 */
int
cleanup_memory_driver(void)
{
	int errors, count;
        char command[BUFSIZE];

	count = errors = 0;

	close(pc->mfd);
	if (file_exists(pc->memory_device, NULL) &&
	    unlink(pc->memory_device)) {
                error(INFO, "%s: %s\n", pc->memory_device, strerror(errno));
		errors++;
	}

	if (!(pc->flags & MODPRELOAD) && 
	    memory_driver_module_loaded(&count) && !count) {
	        sprintf(command, "/sbin/rmmod %s", pc->memory_module);
		if (CRASHDEBUG(1))
			fprintf(fp, "%s\n", command);
		errors += system(command);
	}

	if (errors)
		error(NOTE, "cleanup_memory_driver failed\n");

	return errors ? FALSE : TRUE;
}


/*
 *  Use the kernel's radix_tree_lookup() function as a template to dump
 *  a radix tree's entries, which depends upon the likelihood that the
 *  following items in the kernel's radix tree library will remain unchanged.
 */

#define RADIX_TREE_MAP_SHIFT  6
#define RADIX_TREE_MAP_SIZE  (1UL << RADIX_TREE_MAP_SHIFT)
#define RADIX_TREE_MAP_MASK  (RADIX_TREE_MAP_SIZE-1)

struct radix_tree_node {
        unsigned int    count;
        void            *slots[RADIX_TREE_MAP_SIZE];
};

/*
 *  do_radix_tree argument usage: 
 *
 *    root: Address of a radix_tree_root structure
 *
 *    flag: RADIX_TREE_COUNT - Return the number of entries in the tree.   
 *          RADIX_TREE_SEARCH - Search for an entry at rtp->index; if found,
 *            store the entry in rtp->value and return a count of 1; otherwise
 *            return a count of 0. 
 *          RADIX_TREE_DUMP - Dump all existing index/value pairs.    
 *          RADIX_TREE_GATHER - Store all existing index/value pairs in the 
 *            passed-in array of radix_tree_pair structs starting at rtp, 
 *            returning the count of entries stored; the caller can/should 
 *            limit the number of returned entries by putting the array size
 *            (max count) in the rtp->index field of the first structure 
 *            in the passed-in array.
 *
 *     rtp: Unused by RADIX_TREE_COUNT and RADIX_TREE_DUMP. 
 *          A pointer to a radix_tree_pair structure for RADIX_TREE_SEARCH.
 *          A pointer to an array of radix_tree_pair structures for
 *          RADIX_TREE_GATHER; the dimension (max count) of the array may
 *          be stored in the index field of the first structure to avoid
 *          any chance of an overrun.
 */
ulong
do_radix_tree(ulong root, int flag, struct radix_tree_pair *rtp)
{
	int i, ilen, height; 
	long nlen;
	ulong index, maxindex, count, maxcount;
	long *height_to_maxindex;
	char *radix_tree_root_buf;
	struct radix_tree_pair *r;
	ulong root_rnode;
	void *ret;

	if (!VALID_STRUCT(radix_tree_root) || !VALID_STRUCT(radix_tree_node) ||
	    !VALID_MEMBER(radix_tree_root_height) ||
	    !VALID_MEMBER(radix_tree_root_rnode) ||
	    !ARRAY_LENGTH(height_to_maxindex)) 
		error(FATAL, 
		   "radix trees do not exist (or have changed their format)\n");

	if (!(nlen = 
	    datatype_info("radix_tree_node", "slots", MEMBER_SIZE_REQUEST)))
		error(FATAL, 
		    "cannot determine length of radix_tree_node.slots array\n");
	else if ((nlen / sizeof(void *)) != RADIX_TREE_MAP_SIZE)
		error(FATAL, 
		    "unexpected length of radix_tree_node.slots array: %ld\n",
			nlen / sizeof(void *));

	if (SIZE(radix_tree_node) != sizeof(struct radix_tree_node))
		error(FATAL, 
		    "unexpected length of radix_tree_node structure\n");

	ilen = ARRAY_LENGTH(height_to_maxindex);
	height_to_maxindex = (long *)GETBUF(ilen * sizeof(long));
	readmem(symbol_value("height_to_maxindex"), KVADDR, 
	    	height_to_maxindex, ilen*sizeof(long),
		"height_to_maxindex array", FAULT_ON_ERROR);

	if (CRASHDEBUG(1)) {
		fprintf(fp, "radix_tree_node.slots[%ld]\n", 
			nlen/sizeof(void *));
		fprintf(fp, "height_to_maxindex[%d]: ", ilen);
		for (i = 0; i < ilen; i++)
			fprintf(fp, "%lu ", height_to_maxindex[i]);
		fprintf(fp, "\n");
		fprintf(fp, "radix_tree_root at %lx:\n", root);
		dump_struct("radix_tree_root", (ulong)root, RADIX(16));
	}

	radix_tree_root_buf = GETBUF(SIZE(radix_tree_root));
	readmem(root, KVADDR, radix_tree_root_buf, SIZE(radix_tree_root),
		"radix_tree_root", FAULT_ON_ERROR);
	height = UINT(radix_tree_root_buf + OFFSET(radix_tree_root_height));

	if (height > ilen) {
		fprintf(fp, "radix_tree_root at %lx:\n", root);
		dump_struct("radix_tree_root", (ulong)root, RADIX(16));
		error(FATAL, 
                   "height %d is greater than height_to_maxindex[] index %ld\n",
			height, ilen);
	}
	
	maxindex = height_to_maxindex[height];
	FREEBUF(height_to_maxindex);

	root_rnode = root + OFFSET(radix_tree_root_rnode);

	switch (flag)
	{
	case RADIX_TREE_COUNT:
		for (index = count = 0; index <= maxindex; index++) {
			if (radix_tree_lookup(root_rnode, index, height))
				count++;
		}
		break;

	case RADIX_TREE_SEARCH:
		count = 0;
		if (rtp->index > maxindex) 
			break;

		if ((ret = radix_tree_lookup(root_rnode, rtp->index, height))) {
			rtp->value = ret;
			count++;
		}
		break;

	case RADIX_TREE_DUMP:
		for (index = count = 0; index <= maxindex; index++) {
			if ((ret = 
			    radix_tree_lookup(root_rnode, index, height))) {
				fprintf(fp, "[%ld] %lx\n", index, (ulong)ret);
				count++;
			}
		}
		break;

	case RADIX_TREE_GATHER:
		if (!(maxcount = rtp->index))
			maxcount = (ulong)(-1);   /* caller beware */

                for (index = count = 0, r = rtp; index <= maxindex; index++) {
                        if ((ret = 
			    radix_tree_lookup(root_rnode, index, height))) {
				r->index = index;
				r->value = ret;
				count++;
                                if (--maxcount <= 0)
					break;
				r++;
                        }
                }
		break;

	default:
		error(FATAL, "do_radix_tree: invalid flag: %lx\n", flag);
	}

	return count;
}

static void *
radix_tree_lookup(ulong root_rnode, ulong index, int height)
{
	unsigned int shift;
	struct radix_tree_node **slot;
	struct radix_tree_node slotbuf;
	void **kslotp, **uslotp;

	shift = (height-1) * RADIX_TREE_MAP_SHIFT;
	kslotp = (void **)root_rnode;

	while (height > 0) {
		readmem((ulong)kslotp, KVADDR, &slot, sizeof(void *),
			"radix_tree_node slot", FAULT_ON_ERROR);

		if (slot == NULL)
			return NULL;

		readmem((ulong)slot, KVADDR, &slotbuf, 
			sizeof(struct radix_tree_node),
			"radix_tree_node struct", FAULT_ON_ERROR);

		uslotp = (void **)
		    (slotbuf.slots + ((index >> shift) & RADIX_TREE_MAP_MASK));
		kslotp = *uslotp;

		shift -= RADIX_TREE_MAP_SHIFT;
		height--;
	}

	return (void *) kslotp;
}

int
is_readable(char *filename)
{
	int fd;

        if ((fd = open(filename, O_RDONLY)) < 0) {
		error(INFO, "%s: %s\n", filename, strerror(errno));
		return FALSE;
	} else
		close(fd);

	return TRUE;
}
