/* filesys.c - core analysis suite
 *
 * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
 * Copyright (C) 2002-2019 David Anderson
 * Copyright (C) 2002-2019 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 <sys/sysmacros.h>
#include <linux/major.h>
#include <regex.h>
#include <sys/utsname.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 build_kernel_directory(char *);
static int redhat_kernel_directory_v1(char *);
static int redhat_kernel_directory_v2(char *);
static int redhat_debug_directory(char *);
static ulong *create_dentry_array(ulong, int *);
static ulong *create_dentry_array_percpu(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 *);
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 int match_file_string(char *, char *, char *);
static ulong get_root_vfsmount(char *);
static void check_live_arch_mismatch(void);
static long get_inode_nrpages(ulong);
static void dump_inode_page_cache_info(ulong);

#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;

/*
 *  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 (XEN_HYPER_MODE() && !pc->dumpfile)
				error(FATAL, 
				    "Xen hypervisor mode requires a dumpfile\n");

			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 (!pc->dumpfile)
				check_live_arch_mismatch();
			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 (LOCAL_ACTIVE() && !(pc->namelist_debug || pc->system_map)) {
			memory_source_init();
			match_proc_version();
		}
	
	}

	memory_source_init();

	if (ACTIVE())
		proc_kcore_init(fp, UNUSED);

	if (CRASHDEBUG(1)) {
		fprintf(fp, "readmem: %s() ", readmem_function_name());
		if (ACTIVE()) {
			fprintf(fp, "-> %s ", pc->live_memsrc);
			if (pc->flags & MEMMOD)
				fprintf(fp, "(module)");
			else if (pc->flags & CRASHBUILTIN)
				fprintf(fp, "(built-in)");
		}
		fprintf(fp, "\n");
	}
}

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

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return;

        if (LOCAL_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 if (STREQ(pc->live_memsrc, "/proc/kcore")) {
			if ((pc->mfd = open("/proc/kcore", O_RDONLY)) < 0)
				error(FATAL, "/proc/kcore: %s\n", 
					strerror(errno));
			if (!proc_kcore_init(fp, pc->mfd))
				error(FATAL, 
				    "/proc/kcore: initialization failed\n");
		} else {
			if (!pc->live_memsrc)
				error(FATAL, "cannot find a live memory device\n");
			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 & KVMDUMP) {
                        if (!kvmdump_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);
		} else if (pc->flags & VMWARE_VMSS) {
			if (pc->flags2 & VMWARE_VMSS_GUESTDUMP) {
				if (!vmware_guestdump_init(pc->dumpfile, fp))
					error(FATAL, "%s: initialization failed\n",
						pc->dumpfile);
			} else {
				if (!vmware_vmss_init(pc->dumpfile, fp))
					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 buffer[BUFSIZE], *p1, *p2;

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return;

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

	if (match_file_string(pc->namelist, kt->proc_version, buffer)) {
                if (CRASHDEBUG(1)) {
			fprintf(fp, "/proc/version:\n%s\n", 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         " : " ");

	/*
	 *  find_booted_system_map() requires VTOP(), which used to be a 
	 *  hardwired masking of the kernel address.  But some architectures 
	 *  may not know what their physical base address is at this point, 
	 *  and others may have different machdep->kvbase values, so for all
	 *  but the 0-based kernel virtual address architectures, bail out
	 *  here with a relevant error message.
	 */
	if (!machine_type("S390") && !machine_type("S390X")) {
		p1 = &kt->proc_version[strlen("Linux version ")];
		p2 = strstr(p1, " ");
		*p2 = NULLCHAR;
		error(WARNING, "/proc/version indicates kernel version: %s\n", p1);
		error(FATAL, "please use the vmlinux file for that kernel version, or try using\n"
			"       the System.map for that kernel version as an additional argument.\n", p1);
		clean_exit(1);
	}

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


#define CREATE  1
#define DESTROY 0
#define DEFAULT_SEARCHDIRS 6
#define EXTRA_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",
		"/usr/lib/debug/boot/",
        	"/",
        	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 four directories: 
	 *
	 *    /lib/modules
	 *    /usr/src/redhat/BUILD/kernel-<version>/linux
	 *    /usr/src/redhat/BUILD/kernel-<version>/linux-<version>
	 *    /usr/lib/debug/lib/modules
	 *
	 */  
	cnt = DEFAULT_SEARCHDIRS + EXTRA_SEARCHDIRS;  

        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));
			return default_searchdirs;
		} 
		for (i = 0; i < DEFAULT_SEARCHDIRS; i++) 
			searchdirs[i] = default_searchdirs[i];
		cnt = DEFAULT_SEARCHDIRS;
	}

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

        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
build_kernel_directory(char *buf)
{
	char *p1, *p2;

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

	BZERO(buf, BUFSIZE);
	sprintf(buf, "/lib/modules/");

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

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

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

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 buffer[BUFSIZE];
	char **searchdirs;
	int i, preferred, wrapped;
        DIR *dirp;
        struct dirent *dp;
	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_kernel(kernel))
				continue;

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

			found = match_file_string(kernel, kt->proc_version, buffer);
	
			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++;
		}
		fclose(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++;
			}
                }
		fclose(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) {
		fclose(version);
                return FALSE;
	}
        
        fclose(version);

	strip_linefeeds(kt->proc_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 buffer[BUFSIZE];
	ulong value;
	struct new_utsname new_utsname;

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

	if (!match_file_string(system_map, "D system_utsname", buffer))
		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)
{
	char 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, int follow_links)
{
	char command[BUFSIZE];
	char buf[BUFSIZE];
	char *retbuf, *start, *end, *module;
	FILE *pipe;
	regex_t regex;
	int regex_used, 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 %s -name %s -print; /bin/echo search done",
		follow_links ? "-L" : "", directory, file);

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

	done = FALSE;
	retbuf = NULL;
	regex_used = ((start = strstr(file, "[")) && 
		(end = strstr(file, "]")) && (start < end) &&
		(regcomp(&regex, file, 0) == 0));

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

	if (regex_used)
		regfree(&regex);

        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)
{
	int ret ATTRIBUTE_UNUSED;

        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);
	ret = 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)
{
	int ret ATTRIBUTE_UNUSED;

	if (pc->tmpfile) {
		fflush(pc->tmpfile);
		ret = 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(), set_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 the 
 *  global fp pointer has to explicitly 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,
 *  or alternatively a caller may pass in a FILE pointer to set_tmpfile2().
 */
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;
	}
}

void
set_tmpfile2(FILE *fptr)
{
        if (pc->tmpfile2)
                error(FATAL, "secondary temporary file already in use\n");

	pc->tmpfile2 = fptr;
}


#define MOUNT_PRINT_INODES  0x1
#define MOUNT_PRINT_FILES   0x2

/*
 *  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, mount, 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 value1, value2;
	char *spec_string;
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	char *arglist[MAXARGS*2];
	ulong vfsmount = 0;
	int flags = 0;
	int save_next;
	ulong pid;

	/* find a context */
	pid = 1;
	while ((namespace_context = pid_to_context(pid)) == NULL)
		pid++;

	while ((c = getopt(argcnt, args, "ifn:")) != EOF) {
		switch(c)
		{
		case 'i':
			if (INVALID_MEMBER(super_block_s_dirty)) {
				error(INFO, 
				    "the super_block.s_dirty linked list does "
				    "not exist in this kernel\n");
				option_not_supported(c);
			}
			flags |= MOUNT_PRINT_INODES;
			break;

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

		case 'n':
			switch (str_to_context(optarg, &value1, &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] == 0) {
		show_mounts(0, flags, namespace_context);
		return;
	}

	/*
	 *  Dump everything into a tmpfile, and then walk
	 *  through it for each search argument entered.
	 */
	open_tmpfile();
	show_mounts(0, MOUNT_PRINT_FILES | 
		(VALID_MEMBER(super_block_s_dirty) ? MOUNT_PRINT_INODES : 0), 
		namespace_context);

	pc->curcmd_flags &= ~HEADER_PRINTED;

	do {
		spec_string = args[optind];
		if (STRNEQ(spec_string, "0x") && 
		    hexadecimal(spec_string, 0))
			shift_string_left(spec_string, 2);

		found = FALSE;
		rewind(pc->tmpfile);
		save_next = 0;

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

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

			for (i = 0; i < c; i++) {
				if (PATHEQ(arglist[i], spec_string))
					found = TRUE;
				/*
				 *  Check for a vfsmount address
				 *  embedded in a struct mount.
				 */
				if ((i == 0) && (c == 5) &&
				    VALID_MEMBER(mount_mnt) &&
				    hexadecimal(spec_string, 0) &&
				    hexadecimal(arglist[i], 0)) {
					value1 = htol(spec_string, 
						FAULT_ON_ERROR, NULL);
					value2 = htol(arglist[i], 
						FAULT_ON_ERROR, NULL) + 
						OFFSET(mount_mnt);
					if (value1 == value2)
						found = TRUE;
				}
			}
			if (found) {
				fp = pc->saved_fp;
				if (flags) {
					sscanf(buf2,"%lx", &vfsmount);
					show_mounts(vfsmount, flags, 
						namespace_context);
				} else {
					if (!(pc->curcmd_flags & HEADER_PRINTED)) {
						fprintf(fp, "%s", mount_hdr);
						pc->curcmd_flags |= HEADER_PRINTED;
					}
					fprintf(fp, "%s", buf2);
				}
				found = FALSE;
				fp = pc->tmpfile;
			}
		}
	} while (args[++optind]);

	close_tmpfile();
}

/*
 *  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*2];
	char buf2[BUFSIZE];
	char buf3[BUFSIZE];
	char buf4[BUFSIZE/2];
	ulong *dentry_list, *dp, *mntlist;
	ulong *vfsmnt;
	char *vfsmount_buf, *super_block_buf, *mount_buf;
	ulong dentry, inode, inode_sb, mnt_parent;
	char *dentry_buf, *inode_buf;
	int cnt, i, m, files_header_printed;
	int mount_cnt; 
	int devlen;
	char mount_files_header[BUFSIZE];
	long per_cpu_s_files;

        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));
		
	dirp = dentry = mnt_parent = sb_s_files = s_dirty = 0;

	if (VALID_MEMBER(super_block_s_dirty))
		s_dirty = OFFSET(super_block_s_dirty);

	per_cpu_s_files = MEMBER_EXISTS("file", "f_sb_list_cpu");

	dentry_list = NULL;
	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); 

	devlen = strlen("DEVNAME")+2;

	if (!strlen(mount_hdr)) {
		snprintf(mount_hdr, sizeof(mount_hdr), "%s %s %s %s DIRNAME\n",
                	mkstring(buf1, VADDR_PRLEN, CENTER, 
				VALID_STRUCT(mount) ?  "MOUNT" : "VFSMOUNT"),
                	mkstring(buf2, VADDR_PRLEN, CENTER, "SUPERBLK"),
                	mkstring(buf3, strlen("rootfs"), LJUST, "TYPE"),
			mkstring(buf4, devlen, LJUST, "DEVNAME"));
	}

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

	sb_s_files = VALID_MEMBER(super_block_s_files) ?
		OFFSET(super_block_s_files) : INVALID_OFFSET;

	if ((flags & MOUNT_PRINT_FILES) && (sb_s_files == INVALID_OFFSET)) {
		/*
		 *  super_block.s_files deprecated
		 */
		if (!kernel_symbol_exists("inuse_filps")) {
			error(INFO, "the super_block.s_files linked list does "
                                    "not exist in this kernel\n");
			option_not_supported('f');
		}
		/*
	  	 * No open files list in super_block (2.2).  
	  	 * Use inuse_filps list instead.
	  	 */
		dentry_list = create_dentry_array(symbol_value("inuse_filps"), 
			&cnt);
	}

	if (VALID_STRUCT(mount)) {
		mount_buf = GETBUF(SIZE(mount));
		vfsmount_buf = mount_buf + OFFSET(mount_mnt);
	} else {
		mount_buf = NULL;
		vfsmount_buf = GETBUF(SIZE(vfsmount));
	}
	super_block_buf = GETBUF(SIZE(super_block));

	for (m = 0, vfsmnt = mntlist; m < mount_cnt; m++, vfsmnt++) {
		if (VALID_STRUCT(mount)) {
			readmem(*vfsmnt, KVADDR, mount_buf, SIZE(mount),
				"mount buffer", FAULT_ON_ERROR);
			devp = ULONG(mount_buf +  OFFSET(mount_mnt_devname));
		} else {
			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 {
			if (VALID_STRUCT(mount)) {
				mnt_parent = ULONG(mount_buf + 
					OFFSET(mount_mnt_parent));
				dentry = ULONG(mount_buf +  
					OFFSET(mount_mnt_mountpoint));
			} 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 (!IS_KVADDR(sbp)) {
			error(WARNING, "cannot get super_block from vfsmnt: 0x%lx\n", *vfsmnt);
			continue;
		}

		if (flags)
			fprintf(fp, "%s", 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, buf4, (BUFSIZE/2)-1))
			sprintf(buf3, "%-6s ", buf4);
                else
			sprintf(buf3, "unknown ");

		if (read_string(devp, buf1, BUFSIZE-1))
			sprintf(buf4, "%s ", 
				mkstring(buf2, devlen, LJUST, buf1));
		else
			sprintf(buf4, "%s ", 
				mkstring(buf2, devlen, LJUST, "(unknown)"));

		sprintf(buf1, "%s%s", buf3, buf4);
		while ((strlen(buf1) > 17) && (buf1[strlen(buf1)-2] == ' '))
			strip_ending_char(buf1, ' ');
		fprintf(fp, "%s", buf1);

		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, VALID_STRUCT(mount) ?
				mnt_parent + OFFSET(mount_mnt) : mnt_parent);
                       	fprintf(fp, "%-10s\n", buf1);
		}

		if (flags & MOUNT_PRINT_FILES) {
			if (sb_s_files != INVALID_OFFSET) {
				dentry_list = per_cpu_s_files ?
					create_dentry_array_percpu(sbp+
					    sb_s_files, &cnt) :
					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, "%s", 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); 
	if (VALID_STRUCT(mount))
		FREEBUF(mount_buf);
	else
		FREEBUF(vfsmount_buf);
	FREEBUF(super_block_buf);
}

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

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

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

		/* Linux 6.8 and later keep list of mounts in an rbtree. */
		if (VALID_MEMBER(mnt_namespace_nr_mounts)) {
			uint nr_mounts;
			ulong *mntlist, *l;
			struct rb_root *mounts;
			struct rb_node *node;

			readmem(mnt_ns + OFFSET(mnt_namespace_nr_mounts), KVADDR, &nr_mounts,
				sizeof(uint), "mnt_namespace.nr_mounts", FAULT_ON_ERROR);

			if (!nr_mounts)
				return NULL;

			mounts = (struct rb_root *)(mnt_ns + OFFSET(mnt_namespace_mounts));

			mntlist = (ulong *)GETBUF(sizeof(ulong) * nr_mounts);
			l = mntlist;
			for (node = rb_first(mounts); node; l++, node = rb_next(node))
				*l = (ulong)node - OFFSET(mount_mnt_node);

			*cntptr = nr_mounts;
			return mntlist;
		}

        	if (!readmem(mnt_ns + OFFSET(mnt_namespace_root), KVADDR, 
			&root, sizeof(void *), "mnt_namespace root", 
			RETURN_ON_ERROR|QUIET))
			error(FATAL, "cannot determine mount list location!\n");

		ld->start = root + OFFSET_OPTION(vfsmount_mnt_list, mount_mnt_list);
        	ld->end = mnt_ns + OFFSET(mnt_namespace_list);

	} 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_OPTION(vfsmount_mnt_list, mount_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 if (VALID_STRUCT(mount))
		ld->list_head_offset = OFFSET(mount_mnt_list);
	else
                ld->member_offset = OFFSET(vfsmount_mnt_next);
        
        *cntptr = do_list(ld);
        return(ld->list_ptr);
}


/*
 *  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, *mount_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 {
		inode_buf = NULL;
		superblock = ULONG(dentry_buf + OFFSET(dentry_d_sb));
	}

	if (!superblock)
		goto nopath;

        if (VALID_MEMBER(file_f_vfsmnt)) {
		mntlist = get_mount_list(&mount_cnt, pid_to_context(1));
		if (VALID_STRUCT(mount)) {
			mount_buf = GETBUF(SIZE(mount));
			vfsmount_buf = mount_buf + OFFSET(mount_mnt);
		} else {
			mount_buf = NULL;
			vfsmount_buf = GETBUF(SIZE(vfsmount));
		}

        	for (m = found = 0, vfsmnt = mntlist; 
		     m < mount_cnt; m++, vfsmnt++) {
			if (VALID_STRUCT(mount))
				readmem(*vfsmnt, KVADDR, mount_buf, SIZE(mount),
					"mount buffer", FAULT_ON_ERROR);
			else
				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,
					VALID_STRUCT(mount) ?
					*vfsmnt+OFFSET(mount_mnt) : *vfsmnt);
				found = TRUE;
			}
		}

		if (!found && symbol_exists("pipe_mnt")) {
			get_symbol_data("pipe_mnt", sizeof(long), &vfs);
			if (VALID_STRUCT(mount))
				readmem(vfs - OFFSET(mount_mnt), KVADDR, mount_buf, SIZE(mount),
					"mount buffer", FAULT_ON_ERROR);
			else
				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);
			if (VALID_STRUCT(mount))
				readmem(vfs - OFFSET(mount_mnt), KVADDR, mount_buf, SIZE(mount),
					"mount buffer", FAULT_ON_ERROR);
			else
				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);
		if (VALID_STRUCT(mount))
			FREEBUF(mount_buf);
		else
			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;
}

/*
 *  Walk each per-cpu open file list and return an array of open dentries.
 */
static ulong *
create_dentry_array_percpu(ulong percpu_list_addr, int *count)
{
	int i, j, c, total;
	int cpu; 
	ulong percpu_list_offset, list_addr;
	ulong *dentry_list;
	struct percpu_list {
		ulong *dentry_list;
		int count;
	} *percpu_list;

	if ((cpu = get_highest_cpu_online()) < 0)
		error(FATAL, "cannot determine highest cpu online\n");

	percpu_list = (struct percpu_list *)
		GETBUF(sizeof(struct percpu_list) * (cpu+1));

        readmem(percpu_list_addr, KVADDR, &percpu_list_offset, sizeof(void *), 
	    "percpu file list head offset", FAULT_ON_ERROR);

	for (c = total = 0; c < (cpu+1); c++) {
		list_addr = percpu_list_offset + kt->__per_cpu_offset[c];
		percpu_list[c].dentry_list = create_dentry_array(list_addr, 
			&percpu_list[c].count);
		total += percpu_list[c].count;
	}

	if (total) {
		dentry_list = (ulong *)GETBUF(total * sizeof(ulong));

		for (c = i = 0; c < (cpu+1); c++) {
			if (percpu_list[c].count == 0)
				continue;
			for (j = 0; j < percpu_list[c].count; j++)
				dentry_list[i++] = 
					percpu_list[c].dentry_list[j];
			FREEBUF(percpu_list[c].dentry_list);
		}
	} else 
		dentry_list = NULL;

	FREEBUF(percpu_list);
	*count = total;
	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(file_f_inode, "file", "f_inode");
	MEMBER_OFFSET_INIT(path_mnt, "path", "mnt");
	MEMBER_OFFSET_INIT(path_dentry, "path", "dentry");
	if (INVALID_MEMBER(file_f_dentry)) {
		MEMBER_OFFSET_INIT(file_f_path, "file", "f_path");
		ASSIGN_OFFSET(file_f_dentry) = OFFSET(file_f_path) + OFFSET(path_dentry);
		ASSIGN_OFFSET(file_f_vfsmnt) = OFFSET(file_f_path) + OFFSET(path_mnt);
	}
	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(dentry_d_sb, "dentry", "d_sb");
	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");
	if (INVALID_MEMBER(qstr_len))
		ANON_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");
	if (INVALID_MEMBER(vfsmount_mnt_devname))
		MEMBER_OFFSET_INIT(mount_mnt_devname, "mount", "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");
	if (INVALID_MEMBER(vfsmount_mnt_devname))
		MEMBER_OFFSET_INIT(mount_mnt_list, "mount", "mnt_list");
        MEMBER_OFFSET_INIT(vfsmount_mnt_parent, "vfsmount", "mnt_parent");
	if (INVALID_MEMBER(vfsmount_mnt_devname))
		MEMBER_OFFSET_INIT(mount_mnt_parent, "mount", "mnt_parent");
        MEMBER_OFFSET_INIT(vfsmount_mnt_mountpoint, 
		"vfsmount", "mnt_mountpoint");
	if (INVALID_MEMBER(vfsmount_mnt_devname))
		MEMBER_OFFSET_INIT(mount_mnt_mountpoint,
			"mount", "mnt_mountpoint");
	MEMBER_OFFSET_INIT(mount_mnt, "mount", "mnt");
	MEMBER_OFFSET_INIT(namespace_root, "namespace", "root");
	MEMBER_OFFSET_INIT(task_struct_nsproxy, "task_struct", "nsproxy");
	if (VALID_MEMBER(namespace_root)) {
		MEMBER_OFFSET_INIT(namespace_list, "namespace", "list");
		MEMBER_OFFSET_INIT(task_struct_namespace, 
			"task_struct", "namespace");
	} else if (VALID_MEMBER(task_struct_nsproxy)) {
		MEMBER_OFFSET_INIT(nsproxy_mnt_ns, "nsproxy", "mnt_ns");
        	MEMBER_OFFSET_INIT(mnt_namespace_root, "mnt_namespace", "root");
        	MEMBER_OFFSET_INIT(mnt_namespace_list, "mnt_namespace", "list");
		/* Linux 6.8 and later */
		MEMBER_OFFSET_INIT(mnt_namespace_mounts, "mnt_namespace", "mounts");
		MEMBER_OFFSET_INIT(mnt_namespace_nr_mounts, "mnt_namespace", "nr_mounts");
		MEMBER_OFFSET_INIT(mount_mnt_node, "mount", "mnt_node");
	} 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(mount, "mount");
	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");

	MEMBER_OFFSET_INIT(rb_root_rb_node, 
		"rb_root","rb_node");
	MEMBER_OFFSET_INIT(rb_node_rb_left, 
		"rb_node","rb_left");
	MEMBER_OFFSET_INIT(rb_node_rb_right, 
		"rb_node","rb_right");
}

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(fp, "   cached_file[%2d]: %lx (%ld)\n",
                        i, ft->cached_file[i],
                        ft->cached_file_hits[i]);
        fprintf(fp, "        file_cache: %lx\n", (ulong)ft->file_cache);
        fprintf(fp, "  file_cache_index: %d\n", ft->file_cache_index);
        fprintf(fp, "  file_cache_fills: %ld\n", ft->file_cache_fills);

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

        for (i = 0; i < INODE_CACHE; i++)
                fprintf(fp, "  cached_inode[%2d]: %lx (%ld)\n",
                        i, ft->cached_inode[i],
                        ft->cached_inode_hits[i]);
        fprintf(fp, "       inode_cache: %lx\n", (ulong)ft->inode_cache);
        fprintf(fp, " inode_cache_index: %d\n", ft->inode_cache_index);
        fprintf(fp, " 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(fp, "     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(fp, "   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(fp, "    inode hit rate: %2ld%% (%ld of %ld)\n",
                        (ihits * 100)/ft->inode_cache_fills,
                        ihits, ft->inode_cache_fills);
	}
}

/*
 * Get the page count for the specific mapping
 */
static long
get_inode_nrpages(ulong i_mapping)
{
	char *address_space_buf;
	ulong nrpages;

	address_space_buf = GETBUF(SIZE(address_space));

	readmem(i_mapping, KVADDR, address_space_buf,
	    SIZE(address_space), "address_space buffer",
	    FAULT_ON_ERROR);
	nrpages = ULONG(address_space_buf + OFFSET(address_space_nrpages));

	FREEBUF(address_space_buf);

	return nrpages;
}

static void
dump_inode_page_cache_info(ulong inode)
{
	char *inode_buf;
	ulong i_mapping, nrpages, root_rnode, xarray, count;
	struct list_pair lp;
	char header[BUFSIZE];
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];

	inode_buf = GETBUF(SIZE(inode));
	readmem(inode, KVADDR, inode_buf, SIZE(inode), "inode buffer",
	    FAULT_ON_ERROR);

	i_mapping = ULONG(inode_buf + OFFSET(inode_i_mapping));
	nrpages = get_inode_nrpages(i_mapping);

	sprintf(header, "%s  NRPAGES\n",
		mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "INODE"));
	fprintf(fp, "%s", header);

	fprintf(fp, "%s  %s\n\n",
		mkstring(buf1, VADDR_PRLEN,
		CENTER|RJUST|LONG_HEX,
		MKSTR(inode)),
		mkstring(buf2, strlen("NRPAGES"),
		RJUST|LONG_DEC,
		MKSTR(nrpages)));

	FREEBUF(inode_buf);

	if (!nrpages)
		return;

	xarray = root_rnode = count = 0;
	if (MEMBER_EXISTS("address_space", "i_pages") &&
	    (STREQ(MEMBER_TYPE_NAME("address_space", "i_pages"), "xarray") ||
	    (STREQ(MEMBER_TYPE_NAME("address_space", "i_pages"), "radix_tree_root") &&
	     MEMBER_EXISTS("radix_tree_root", "xa_head"))))
		xarray = i_mapping + OFFSET(address_space_page_tree);
	else 
		root_rnode = i_mapping + OFFSET(address_space_page_tree);

	lp.index = 0;
	lp.value = (void *)&dump_inode_page;

	if (root_rnode)
		count = do_radix_tree(root_rnode, RADIX_TREE_DUMP_CB, &lp);
	else if (xarray)
		count = do_xarray(xarray, XARRAY_DUMP_CB, &lp);

	if (count != nrpages)
		error(INFO, "%s page count: %ld  nrpages: %ld\n",
			root_rnode ? "radix tree" : "xarray",
			count, nrpages);

	return;
}

/*
 *  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.
 */

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

        ref = NULL;
        refarg = NULL;

        while ((c = getopt(argcnt, args, "d:R:p:c")) != EOF) {
                switch(c)
		{
		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;

		case 'p':
			if (VALID_MEMBER(address_space_page_tree) &&
			    VALID_MEMBER(inode_i_mapping)) {
				value = htol(optarg, FAULT_ON_ERROR, NULL);
				dump_inode_page_cache_info(value);
			} else
				option_not_supported('p');
			return;

		case 'c':
			if (VALID_MEMBER(address_space_nrpages) &&
			    VALID_MEMBER(inode_i_mapping))
				open_flags |= PRINT_NRPAGES;
			else
				option_not_supported('c');
			break;

		default:
			argerrs++;
			break;
		}
	}

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

	if (!args[optind]) {
		if (!ref)
			print_task_header(fp, CURRENT_CONTEXT(), 0);

		open_files_dump(CURRENT_TASK(), open_flags, 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, open_flags, ref);
                                fprintf(fp, "\n");
                        }
                        break;

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

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

		subsequent++;
		optind++;
	}
}

#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, "%s", root_pwd);    \
		root_pwd_printed = TRUE;        \
	}                                       \
	if (!header_printed) {                  \
		fprintf(fp, "%s", files_header);\
                header_printed = TRUE;          \
	}                                       \
	fprintf(fp, "%s", 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;
	char *ret ATTRIBUTE_UNUSED;
	ulong root_dentry, pwd_dentry;
	ulong root_inode, pwd_inode;
	ulong vfsmnt;
	int max_fdset = 0;
	int max_fds = 0;
	ulong open_fds_addr;
	int open_fds_size;
	ulong *open_fds;
	ulong fd;
	ulong file;
	ulong value;
	int i, j, use_path;
	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*4];
	int root_pwd_printed = 0;
	int file_dump_flags = 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);

	if (flags & PRINT_NRPAGES) {
		sprintf(files_header, " FD%s%s%s%s%sNRPAGES%sTYPE%sPATH\n",
			space(MINSPACE),
			mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "INODE"),
			space(MINSPACE),
			mkstring(buf2, MAX(VADDR_PRLEN, strlen("I_MAPPING")),
			BITS32() ? (CENTER|RJUST) : (CENTER|LJUST), "I_MAPPING"),
			space(MINSPACE),
			space(MINSPACE),
			space(MINSPACE));
	} else {
		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);

		use_path = (MEMBER_TYPE("fs_struct", "root") == TYPE_CODE_STRUCT);
		if (use_path)
			root_dentry = ULONG(fs_struct_buf + OFFSET(fs_struct_root) +
				OFFSET(path_dentry));
		else
			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 if (use_path) {
				vfsmnt = ULONG(fs_struct_buf +
					OFFSET(fs_struct_root) +
					OFFSET(path_mnt));
				get_pathname(root_dentry, root_pathname, 
					BUFSIZE, 1, vfsmnt);
			} else {
				get_pathname(root_dentry, root_pathname, 
					BUFSIZE, 1, 0);
			}
		}

		if (use_path)
			pwd_dentry = ULONG(fs_struct_buf + OFFSET(fs_struct_pwd) +
				OFFSET(path_dentry));
		else
			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 if (use_path) {
				vfsmnt = ULONG(fs_struct_buf +
					OFFSET(fs_struct_pwd) +
					OFFSET(path_mnt));
				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); 
			if (VALID_MEMBER(fdtable_max_fdset))
				max_fdset = INT(fdtable_buf +
					OFFSET(fdtable_max_fdset));
			else
				max_fdset = -1;
			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));

	open_fds_size = MAX(max_fdset, max_fds) / BITS_PER_BYTE;	
	open_fds = (ulong *)GETBUF(open_fds_size);
	if (!open_fds) {
		if (fdtable_buf)
			FREEBUF(fdtable_buf);
		FREEBUF(files_struct_buf);
		return;
	}

	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, open_fds_size);
		else
			readmem(open_fds_addr, KVADDR, open_fds,
				open_fds_size, "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);
		FREEBUF(open_fds);
		return;
	}

	file_dump_flags = DUMP_FULL_NAME | DUMP_EMPTY_FILE;
	if (flags & PRINT_NRPAGES)
		file_dump_flags |= DUMP_FILE_NRPAGES;

	j = 0;
	for (;;) {
		unsigned long set;
		i = j * BITS_PER_LONG;
		if (((max_fdset >= 0) && (i >= max_fdset)) || 
		    (i >= max_fds))
			 break;
		set = open_fds[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, file_dump_flags)) {
						BZERO(buf4, BUFSIZE);
						rewind(pc->tmpfile);
						ret = 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, "%s", files_header);
						header_printed = 1;
					}
					file_dump(file, 0, 0, i, file_dump_flags);
				}
			}
			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);
	FREEBUF(open_fds);
}

/*
 *  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++) {
			if (STREQ(arglist[i], "?"))
				continue;
        		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;
}

#ifdef DEPRECATED
/*
 * 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");
}
#endif
	    
/*
 * file_dump() prints info for an open file descriptor
 */

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];
	ulong i_mapping = 0;
	ulong nrpages = 0;

	file_buf = NULL;

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

	if (!dentry) {
		if (flags & DUMP_EMPTY_FILE) {
			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|LONG_HEX|ZERO_FILL, 
				MKSTR(dentry)),
				space(MINSPACE),
				mkstring(buf3, VADDR_PRLEN, 
				CENTER, 
				"?"),
				space(MINSPACE),
				"?   ",
				space(MINSPACE),
				"?");
			return TRUE;
		}
		return FALSE;
	}

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

	if (!inode) { 
		if (flags & DUMP_EMPTY_FILE) {
			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|LONG_HEX|ZERO_FILL, 
				MKSTR(inode)),
				space(MINSPACE),
				"?   ",
				space(MINSPACE),
				"?");
			return TRUE;
		}
		return FALSE;
	}

	inode_buf = fill_inode_cache(inode);

	if (flags & DUMP_FULL_NAME) {
		if (VALID_MEMBER(file_f_vfsmnt)) {
			vfsmnt = get_root_vfsmount(file_buf);
			get_pathname(dentry, pathname, BUFSIZE, 1, vfsmnt);
			if (STRNEQ(pathname, "/pts/") &&
			    STREQ(vfsmount_devname(vfsmnt, buf1, BUFSIZE),
			    "devpts"))
				string_insert("/dev", pathname);
		} 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 if (flags & DUMP_FILE_NRPAGES) {
			i_mapping = ULONG(inode_buf + OFFSET(inode_i_mapping));
			nrpages = get_inode_nrpages(i_mapping);

			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(inode)),
				space(MINSPACE),
				mkstring(buf2, MAX(VADDR_PRLEN, strlen("I_MAPPING")),
				CENTER|RJUST|LONG_HEX,
				MKSTR(i_mapping)),
				space(MINSPACE),
				mkstring(buf3, strlen("NRPAGES"),
				RJUST|LONG_DEC,
				MKSTR(nrpages)),
				space(MINSPACE),
				type,
				space(MINSPACE),
				pathname);
		} 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, *mnt_buf;

	BZERO(buf, BUFSIZE);
	BZERO(tmpname, BUFSIZE);
	BZERO(pathname, length);
	if (VALID_STRUCT(mount)) {
		if (VALID_MEMBER(mount_mnt_mountpoint)) {
			mnt_buf = GETBUF(SIZE(mount));
			vfsmnt_buf = mnt_buf + OFFSET(mount_mnt);
		} else {
			mnt_buf = NULL;
			vfsmnt_buf = NULL;
		}
	} else {
		mnt_buf = NULL;
		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-1);
			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 if (VALID_STRUCT(mount)) {
				if (tmp_vfsmnt) {
					if (strncmp(pathname, "//", 2) == 0)
						shift_string_left(pathname, 1);
                                        readmem(tmp_vfsmnt - OFFSET(mount_mnt),
						KVADDR, mnt_buf,
						SIZE(mount), 
						"mount buffer", 
						FAULT_ON_ERROR);
        				parent = ULONG(mnt_buf + 
					    OFFSET(mount_mnt_mountpoint));
        				mnt_parent = ULONG(mnt_buf + 
					    OFFSET(mount_mnt_parent));
					if ((tmp_vfsmnt - OFFSET(mount_mnt)) == mnt_parent)
						break;
					else
						tmp_vfsmnt = mnt_parent + OFFSET(mount_mnt);
				}
			}
			else {
				parent = ULONG(dentry_buf + 
					OFFSET(dentry_d_covers)); 
			}
		}
						
	} while (tmp_dentry != parent && parent);

	if (mnt_buf)
		FREEBUF(mnt_buf);
	else 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 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;
	char *spec_string, *tmp;
	struct foreach_data foreach_data, *fd;
	char task_buf[BUFSIZE];
	char buf[BUFSIZE];
	char uses[20];
	char fuser_header[BUFSIZE];
	int doing_fds, doing_mmap, len;
	int fuser_header_printed, lockd_header_printed;
	ulong spec_addr;

        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"));

	doing_fds = doing_mmap = 0;
	while (args[optind]) {
		spec_string = args[optind];
		spec_addr = htol(spec_string, RETURN_ON_ERROR|QUIET, NULL);
		if ((spec_addr == BADADDR || !IS_KVADDR(spec_addr)) &&
		    spec_string[0] != '/')
			error(FATAL, "invalid argument: %s\n", 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;
		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,
							"%s", fuser_header);
						fuser_header_printed = 1;
					}
					show_fuser(task_buf, uses);
					BZERO(uses, 20);
				}
				BZERO(task_buf, BUFSIZE);
				strcpy(task_buf, buf);
				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 ((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 (!STREQ(uses, "")) {
			if (!fuser_header_printed) {
				fprintf(pc->saved_fp, "%s", 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 ATTRIBUTE_UNUSED;
        int 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;
	freemem = memtotal = freeswap = swaptotal = 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 2>&1"

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

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

	if (file_readable("/dev/mem"))
		pc->live_memsrc = "/dev/mem";
	else if (file_exists("/proc/kcore", NULL)) {
		pc->flags &= ~DEVMEM;
		pc->flags |= PROC_KCORE;
		pc->live_memsrc = "/proc/kcore";
	}
	use_module = crashbuiltin = 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)) {
			if (strstr(buf, "invalid option") && 
			    (uname(&utsname) == 0)) {
				sprintf(buf, 
				    "/lib/modules/%s/kernel/drivers/char/%s", 
					utsname.release, modname2);
				if (file_exists(buf, &stat1))
					use_module = TRUE;
				else {
					strcat(buf, ".xz");
					if (file_exists(buf, &stat1))
						use_module = TRUE;
				}
				break;
			}
			name = basename(strip_linefeeds(buf));
			if (STREQ(name, modname1) || STREQ(name, modname2)) {
				use_module = TRUE;
				break;
			}
		}

		pclose(pipe);

		if (!use_module && file_exists("/dev/crash", &stat1) && 
		    S_ISCHR(stat1.st_mode))
			crashbuiltin = TRUE;
	}

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

	if (crashbuiltin) {
		pc->flags &= ~(DEVMEM|PROC_KCORE);
		pc->flags |= CRASHBUILTIN;
		pc->readmem = read_memory_device;
		pc->writemem = write_memory_device;
		pc->live_memsrc = pc->memory_device;
		pc->memory_module = NULL;
	}

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 /dev/crash is built into the kernel, just open it.
 *   2. If the module is not already loaded, insmod it.
 *   3. Determine the misc driver minor device number that it was assigned.
 *   4. Create (or verify) the device file.
 *   5. Then just open it.
 */ 

static int 
memory_driver_init(void)
{
	dev_t dev;

	if (pc->flags & CRASHBUILTIN)
		goto open_device;

	if (!memory_driver_module_loaded(NULL)) {
	    	if (!insmod_memory_driver_module()) 
			return FALSE;
	} else
		pc->flags |= MODPRELOAD;

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

	if (!create_memory_device(dev)) 
		return FALSE;

open_device:
	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;

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return TRUE;

	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;
}

struct do_radix_tree_info {
	ulong maxcount;
	ulong count;
	void *data;
};
static void do_radix_tree_count(ulong node, ulong slot, const char *path,
				ulong index, void *private)
{
	struct do_radix_tree_info *info = private;
	info->count++;
}
static void do_radix_tree_search(ulong node, ulong slot, const char *path,
				 ulong index, void *private)
{
	struct do_radix_tree_info *info = private;
	struct list_pair *rtp = info->data;

	if (rtp->index == index) {
		rtp->value = (void *)slot;
		info->count = 1;
	}
}
static void do_radix_tree_dump(ulong node, ulong slot, const char *path,
			       ulong index, void *private)
{
	struct do_radix_tree_info *info = private;
	fprintf(fp, "[%ld] %lx\n", index, slot);
	info->count++;
}
static void do_radix_tree_gather(ulong node, ulong slot, const char *path,
				 ulong index, void *private)
{
	struct do_radix_tree_info *info = private;
	struct list_pair *rtp = info->data;

	if (info->maxcount) {
		rtp[info->count].index = index;
		rtp[info->count].value = (void *)slot;

		info->count++;
		info->maxcount--;
	}
}
static void do_radix_tree_dump_cb(ulong node, ulong slot, const char *path,
				  ulong index, void *private)
{
	struct do_radix_tree_info *info = private;
	struct list_pair *rtp = info->data;
	int (*cb)(ulong) = rtp->value;

	/* Caller defined operation */
	if (!cb(slot)) {
		if ((slot & RADIX_TREE_ENTRY_MASK) == RADIX_TREE_EXCEPTIONAL_ENTRY) {
			if (CRASHDEBUG(1))
				error(INFO, "RADIX_TREE_EXCEPTIONAL_ENTRY: %lx\n", slot); 
			return;
		}
		error(FATAL, "do_radix_tree: callback "
		      "operation failed: entry: %ld  item: %lx\n",
		      info->count, slot);
	}
	info->count++;
}

/*
 *  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 list_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.
 *          RADIX_TREE_DUMP_CB - Similar with RADIX_TREE_DUMP, but for each
 *            radix tree entry, a user defined callback at rtp->value will
 *            be invoked.
 *
 *     rtp: Unused by RADIX_TREE_COUNT and RADIX_TREE_DUMP. 
 *          A pointer to a list_pair structure for RADIX_TREE_SEARCH.
 *          A pointer to an array of list_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.
 *          For RADIX_TREE_DUMP_CB, the rtp->value must be initialized as a
 *          callback function.  The callback prototype must be: int (*)(ulong);
 */
ulong
do_radix_tree(ulong root, int flag, struct list_pair *rtp)
{
	struct do_radix_tree_info info = {
		.count		= 0,
		.data		= rtp,
	};
	struct radix_tree_ops ops = {
		.radix		= 16,
		.private	= &info,
	};

	switch (flag)
	{
	case RADIX_TREE_COUNT:
		ops.entry = do_radix_tree_count;
		break;

	case RADIX_TREE_SEARCH:
		/*
		 * FIXME: do_radix_tree_traverse() traverses whole
		 * radix tree, not binary search. So this search is
		 * not efficient.
		 */
		ops.entry = do_radix_tree_search;
		break;

	case RADIX_TREE_DUMP:
		ops.entry = do_radix_tree_dump;
		break;

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

		ops.entry = do_radix_tree_gather;
		break;

	case RADIX_TREE_DUMP_CB:
		if (rtp->value == NULL) {
			error(FATAL, "do_radix_tree: need set callback function");
			return -EINVAL;
		}
		ops.entry = do_radix_tree_dump_cb;
		break;

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

	do_radix_tree_traverse(root, 1, &ops);
	return info.count;
}


struct do_xarray_info {
	ulong maxcount;
	ulong count;
	void *data;
};
static void do_xarray_count(ulong node, ulong slot, const char *path,
				ulong index, void *private)
{
	struct do_xarray_info *info = private;
	info->count++;
}
static void do_xarray_search(ulong node, ulong slot, const char *path,
				 ulong index, void *private)
{
	struct do_xarray_info *info = private;
	struct list_pair *xp = info->data;

	if (xp->index == index) {
		xp->value = (void *)slot;
		info->count = 1;
	}
}
static void do_xarray_dump(ulong node, ulong slot, const char *path,
			       ulong index, void *private)
{
	struct do_xarray_info *info = private;
	fprintf(fp, "[%ld] %lx\n", index, slot);
	info->count++;
}
static void do_xarray_gather(ulong node, ulong slot, const char *path,
				 ulong index, void *private)
{
	struct do_xarray_info *info = private;
	struct list_pair *xp = info->data;

	if (info->maxcount) {
		xp[info->count].index = index;
		xp[info->count].value = (void *)slot;

		info->count++;
		info->maxcount--;
	}
}
static void do_xarray_dump_cb(ulong node, ulong slot, const char *path,
				  ulong index, void *private)
{
	struct do_xarray_info *info = private;
	struct list_pair *xp = info->data;
	int (*cb)(ulong) = xp->value;

	/* Caller defined operation */
	if (!cb(slot)) {
		if (slot & XARRAY_TAG_MASK) {
			if (CRASHDEBUG(1))
				error(INFO, "entry has XARRAY_TAG_MASK bits set: %lx\n", slot); 
			return;
		}
		error(FATAL, "do_xarray: callback "
		      "operation failed: entry: %ld  item: %lx\n",
		      info->count, slot);
	}
	info->count++;
}

/*
 *  do_xarray argument usage: 
 *
 *    root: Address of a xarray structure
 *
 *    flag: XARRAY_COUNT - Return the number of entries in the tree.   
 *          XARRAY_SEARCH - Search for an entry at xp->index; if found,
 *            store the entry in xp->value and return a count of 1; otherwise
 *            return a count of 0. 
 *          XARRY_DUMP - Dump all existing index/value pairs.    
 *          XARRAY_GATHER - Store all existing index/value pairs in the 
 *            passed-in array of list_pair structs starting at xp, 
 *            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 xp->index field of the first structure 
 *            in the passed-in array.
 *          XARRAY_DUMP_CB - Similar with XARRAY_DUMP, but for each
 *            xarray entry, a user defined callback at xp->value will
 *            be invoked.
 *
 *      xp: Unused by XARRAY_COUNT and XARRAY_DUMP. 
 *          A pointer to a list_pair structure for XARRAY_SEARCH.
 *          A pointer to an array of list_pair structures for
 *          XARRAY_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.
 *          For XARRAY_DUMP_CB, the rtp->value must be initialized as a
 *          callback function.  The callback prototype must be: int (*)(ulong);
 */
ulong
do_xarray(ulong root, int flag, struct list_pair *xp)
{
	struct do_xarray_info info = {
		.count		= 0,
		.data		= xp,
	};
	struct xarray_ops ops = {
		.radix		= 16,
		.private	= &info,
	};

	switch (flag)
	{
	case XARRAY_COUNT:
		ops.entry = do_xarray_count;
		break;

	case XARRAY_SEARCH:
		ops.entry = do_xarray_search;
		break;

	case XARRAY_DUMP:
		ops.entry = do_xarray_dump;
		break;

	case XARRAY_GATHER:
		if (!(info.maxcount = xp->index))
			info.maxcount = (ulong)(-1);   /* caller beware */

		ops.entry = do_xarray_gather;
		break;

	case XARRAY_DUMP_CB:
		if (xp->value == NULL) {
			error(FATAL, "do_xarray: no callback function specified");
			return -EINVAL;
		}
		ops.entry = do_xarray_dump_cb;
		break;

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

	do_xarray_traverse(root, 1, &ops);
	return info.count;
}

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;
}

static int
match_file_string(char *filename, char *string, char *buffer)
{
	int found;
	char command[BUFSIZE];
	FILE *pipe;


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

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

	return found;
}

char *
vfsmount_devname(ulong vfsmnt, char *buf, int maxlen)
{
	ulong devp;

	BZERO(buf, maxlen);

	if (VALID_STRUCT(mount)) {
		if (!readmem(vfsmnt - OFFSET(mount_mnt) + OFFSET(mount_mnt_devname),
		    KVADDR, &devp, sizeof(void *), "mount mnt_devname", 
		    QUIET|RETURN_ON_ERROR))
			return buf;
	} else {
		if (!readmem(vfsmnt + OFFSET(vfsmount_mnt_devname),
		    KVADDR, &devp, sizeof(void *), "vfsmount mnt_devname", 
		    QUIET|RETURN_ON_ERROR))
			return buf;
	}

	if (read_string(devp, buf, BUFSIZE-1))
		return buf;

	return buf;
}

static ulong
get_root_vfsmount(char *file_buf)
{
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	ulong vfsmnt;
	ulong mnt_parent;

	vfsmnt = ULONG(file_buf + OFFSET(file_f_vfsmnt));

	if (!strlen(vfsmount_devname(vfsmnt, buf1, BUFSIZE)))
		return vfsmnt;

	if (STREQ(buf1, "udev") || STREQ(buf1, "devtmpfs")) {
		if (VALID_STRUCT(mount)) {
			if (!readmem(vfsmnt - OFFSET(mount_mnt) + OFFSET(mount_mnt_parent), KVADDR, 
			    &mnt_parent, sizeof(void *), "mount mnt_parent", 
			    QUIET|RETURN_ON_ERROR))
				return vfsmnt;
		} else {
			if (!readmem(vfsmnt + OFFSET(vfsmount_mnt_parent), KVADDR, 
			    &mnt_parent, sizeof(void *), "vfsmount mnt_parent", 
			    QUIET|RETURN_ON_ERROR))
				return vfsmnt;
		}

		if (!strlen(vfsmount_devname(mnt_parent, buf2, BUFSIZE)))
			return vfsmnt;

		if (STREQ(buf1, "udev") && STREQ(buf2, "udev"))
			return mnt_parent;
		if (STREQ(buf1, "devtmpfs") && STREQ(buf2, "devtmpfs"))
			return mnt_parent;
	}

	return vfsmnt;
}

void
check_live_arch_mismatch(void)
{
	struct utsname utsname;

	if (machine_type("X86") && (uname(&utsname) == 0) &&
	    STRNEQ(utsname.machine, "x86_64"))
                error(FATAL, "compiled for the X86 architecture\n");

#if defined(__i386__) || defined(__x86_64__) 
	if (machine_type("ARM"))
		error(FATAL, "compiled for the ARM architecture\n");
#endif
#ifdef __x86_64__
	if (machine_type("ARM64"))
		error(FATAL, "compiled for the ARM64 architecture\n");
#endif
#ifdef __x86_64__ 
	if (machine_type("PPC64"))
		error(FATAL, "compiled for the PPC64 architecture\n");
#endif
#ifdef __powerpc64__
	if (machine_type("PPC"))
		error(FATAL, "compiled for the PPC architecture\n");
#endif
}
