[RFC] cacheutils crash extension module

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Dave,

I've written "cacheutils" crash extension module that lists dentry caches and
dumps page caches associated with a specified file path. I think this module
would be helpful for support folks to find and see config/log files in vmcore.

For example:
  $ make extensions

  crash> extend cacheutils.so
  ./cacheutils.so: shared object loaded
  crash> extend
  SHARED OBJECT    COMMANDS
  ./cacheutils.so  ccat cls 
  crash> cls /var/log
  DENTRY           INODE            I_MAPPING        NRPAGES   % PATH
  ffff9c0c3eabe300 ffff9c0c3e875b78 ffff9c0c3e875ce8       0   0 ./
  ffff9c0c16a22900 ffff9c0c16ada2f8 ffff9c0c16ada468       0   0 anaconda/
  ffff9c0c37611000 ffff9c0c3759f5b8 ffff9c0c3759f728       0   0 audit/
  ffff9c0c375ccc00 ffff9c0c3761c8b8 ffff9c0c3761ca28       1 100 btmp
  ffff9c0c28fda240 ffff9c0c22c713f8 ffff9c0c22c71568       6 100 cron
  ffff9c0c3eb7f180 ffff9c0bfd402a78 ffff9c0bfd402be8      36   7 dnf.librepo.log
  ...
  crash> ccat /var/log/messages | tail
  Nov 28 16:43:57 fedora systemd[27015]: Reached target Basic System.
  Nov 28 16:43:57 fedora systemd[27015]: Reached target Default.
  Nov 28 16:43:57 fedora systemd[1]: Started User Manager for UID 0.
  Nov 28 16:43:57 fedora systemd[27015]: Startup finished in 61ms.

It tested OK from RHEL5 (x86_64) to kernel 5.1, which has XArray,
and a recent x86 Fedora.


I have three questions (requests) related to this, and I would appreciate it
if you would take a look at them after your vacation :)

* Is it possible to export the get_mount_list() and get_dump_level() functions
to extension modules? (The patch at the end exports these two functions.)

Now the get_mount_list() is copied from crash/filesys.c to this module.
And I'd like to call the get_dump_level() to inform users whether the "ccat"
command is available or not with their vmcore when the module is loaded.
If they are exported, I will remove the "#define NOT_EXPORTED" and #ifdef
sections in it.

* Is the CRASHDEBUG(0) in do_xarray_dump_cb() function on purpose?

This module uses the function and it sometimes prints the following messages
without crash -d option. The similar message for radix tree is suppressed by
CRASHDEBUG(1), but this has CRASHDEBUG(0), so I'm wondering about it.

  crash> ccat /var/log/messages
  ccat: entry has XARRAY_TAG_MASK bits set: 239ab0024001
  ccat: entry has XARRAY_TAG_MASK bits set: 239ae0024001
  ccat: entry has XARRAY_TAG_MASK bits set: 239af0024001

static void do_xarray_dump_cb(ulong node, ulong slot, const char *path,
                                  ulong index, void *private)
{
...
        if (!cb(slot)) {
                if (slot & XARRAY_TAG_MASK) {
                        if (CRASHDEBUG(0))
                                error(INFO, "entry has XARRAY_TAG_MASK bits set: %lx\n", slot); 
                        return;

* Is it possible to add this module to your crash extension modules page?
I'm planning to put it on GitHub, so I'd like you to add the link to the page.

Thanks,
Kazu

Signed-off-by: Kazuhito Hagio <k-hagio@xxxxxxxxxxxxx>
---
 defs.h     | 2 ++
 diskdump.c | 3 +--
 filesys.c  | 3 +--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/defs.h b/defs.h
index 912037fcc762..42e381e6d9c1 100644
--- a/defs.h
+++ b/defs.h
@@ -5298,6 +5298,7 @@ void set_tmpfile2(FILE *);
 void close_tmpfile2(void);
 void open_files_dump(ulong, int, struct reference *);
 void get_pathname(ulong, char *, int, int, ulong);
+ulong *get_mount_list(int *, struct task_context *);
 char *vfsmount_devname(ulong, char *, int);
 ulong file_to_dentry(ulong);
 ulong file_to_vfsmnt(ulong);
@@ -6440,6 +6441,7 @@ int diskdump_phys_base(unsigned long *);
 int diskdump_set_phys_base(unsigned long);
 ulong *diskdump_flags;
 int is_partial_diskdump(void);
+int get_dump_level(void);
 int dumpfile_is_split(void);
 void show_split_dumpfiles(void);
 void x86_process_elf_notes(void *, unsigned long);
diff --git a/diskdump.c b/diskdump.c
index 694339fbb713..e88243e616cc 100644
--- a/diskdump.c
+++ b/diskdump.c
@@ -80,7 +80,6 @@ struct diskdump_data {
 
 static struct diskdump_data diskdump_data = { 0 };
 static struct diskdump_data *dd = &diskdump_data;
-static int get_dump_level(void);
 
 ulong *diskdump_flags = &diskdump_data.flags;
 
@@ -2114,7 +2113,7 @@ get_diskdump_switch_stack(ulong task)
  *  Version 1 and later compressed kdump dumpfiles contain the dump level
  *  in an additional field of the sub_header_kdump structure.
  */
-static int 
+int
 get_dump_level(void)
 {
 	int dump_level;
diff --git a/filesys.c b/filesys.c
index 037704126840..d88ea28ebe7a 100644
--- a/filesys.c
+++ b/filesys.c
@@ -37,7 +37,6 @@ static int mount_point(char *);
 static int open_file_reference(struct reference *);
 static void memory_source_init(void);
 static int get_pathname_component(ulong, ulong, int, char *, char *);
-static ulong *get_mount_list(int *, struct task_context *);
 char *inode_type(char *, char *);
 static void match_proc_version(void);
 static void get_live_memory_source(void);
@@ -1600,7 +1599,7 @@ show_mounts(ulong one_vfsmount, int flags, struct task_context *namespace_contex
 /*
  *  Allocate and fill a list of the currently-mounted vfsmount pointers.
  */
-static ulong *
+ulong *
 get_mount_list(int *cntptr, struct task_context *namespace_context)
 {
 	struct list_data list_data, *ld;
-- 
2.18.1
/* cacheutils.c - crash extension module for dumping page caches
 *
 * Copyright (C) 2019 NEC Corporation
 *
 * Author: Kazuhito Hagio <k-hagio@xxxxxxxxxxxxx>
 *
 * 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"

/* If things we need are not exported, define this */
#define NOT_EXPORTED

#define CU_INVALID_MEMBER(X)	(cu_offset_table.X == INVALID_OFFSET)
#define CU_OFFSET_INIT(X, Y, Z)	(cu_offset_table.X = MEMBER_OFFSET(Y, Z))
#define CU_OFFSET(X)	(OFFSET_verify(cu_offset_table.X, \
			(char *)__FUNCTION__, __FILE__, __LINE__, #X))

struct cu_offset_table {
	long inode_i_size;
	long vfsmount_mnt_root;
	long dentry_d_subdirs;
	long dentry_d_child;
};
static struct cu_offset_table cu_offset_table = {};

void cacheutils_init(void);
void cacheutils_fini(void);
void cmd_ccat(void);
void cmd_cls(void);

#define DUMP_CACHES		(0x01)
#define DUMP_DONT_SEEK		(0x02)
#define SHOW_INFO		(0x10)
#define SHOW_INFO_DIRS		(0x20)
#define SHOW_INFO_NEG_DENTS	(0x40)
#define SHOW_INFO_DONT_SORT	(0x80)

static char *header_fmt = "%-16s %-16s %-16s %7s %3s %s\n";
static char *dentry_fmt = "%-16lx %-16lx %-16lx %7lu %3d %s%s\n";
static char *negdent_fmt = "%-16lx %-16s %-16s %7s %3s %s\n";

/* Global variables */
static int flags;
static FILE *outfp;
static char *pgbuf;
static ulong nr_written, nr_excluded;
static ulonglong i_size;

static int
dump_slot(ulong slot)
{
	physaddr_t phys;
	ulong index, pos, size;

	if (!is_page_ptr(slot, &phys))
		return FALSE;

	if (!readmem(slot + OFFSET(page_index), KVADDR, &index,
	    sizeof(ulong), "page.index", RETURN_ON_ERROR))
		return FALSE;

	/* 
	 * If the page content was excluded by makedumpfile,
	 * skip it quietly.
	 */
	if (!readmem(phys, PHYSADDR, pgbuf,
	    PAGESIZE(), "page content", RETURN_ON_ERROR|QUIET)) {
		nr_excluded++;
		return TRUE;
	}

	pos = index * PAGESIZE();
	size = (pos + PAGESIZE()) > i_size ? i_size - pos : PAGESIZE();

	if (!(flags & DUMP_DONT_SEEK))
		fseek(outfp, pos, SEEK_SET);

	if (fwrite(pgbuf, sizeof(char), size, outfp) == size)
		nr_written++;
	else if (errno != EPIPE || CRASHDEBUG(1))
		error(INFO, "%lx: write error: %s\n", slot, strerror(errno));

	return TRUE;
}

/* 
 * NOTE: Return a static address. No need to FREEBUF(), but need to
 * strdup() or strncpy() if we want to get another dentry's name with
 * this function before consuming the former one.
 */
static char *
get_dentry_name(ulong dentry, char *dentry_buf)
{
	ulong d_name_name, d_iname;
	static char name[NAME_MAX+1];
	static char unknown[] = "(unknown)";

	BZERO(name, sizeof(name));

	d_name_name = ULONG(dentry_buf + OFFSET(dentry_d_name)
					+ OFFSET(qstr_name));
	d_iname = dentry + OFFSET(dentry_d_iname);

	/*
	 * d_name.name and d_iname are guaranteed NUL-terminated.
	 * See d_alloc() in the kernel.
	 */
	if (d_name_name == d_iname)
		strncpy(name, dentry_buf + OFFSET(dentry_d_iname), NAME_MAX+1);
	else
		if (!readmem(d_name_name, KVADDR, name, NAME_MAX+1,
		    "dentry.d_name.name", RETURN_ON_ERROR))
			return unknown;

	return name;
}

static int
get_inode_info(ulong inode, uint *i_mode, ulong *i_mapping,
		ulonglong *i_size, ulong *nrpages)
{
	char *inode_buf;
	int ret = FALSE;

	inode_buf = GETBUF(SIZE(inode));

	if (!readmem(inode, KVADDR, inode_buf, SIZE(inode),
	    "inode buffer", RETURN_ON_ERROR))
		goto bail_out;

	if (i_mode) {
		if (SIZE(umode_t) == SIZEOF_32BIT)
			*i_mode = UINT(inode_buf + OFFSET(inode_i_mode));
		else
			*i_mode = USHORT(inode_buf + OFFSET(inode_i_mode));
	}
	if (i_mapping)
		*i_mapping = ULONG(inode_buf + OFFSET(inode_i_mapping));
	if (i_size)
		*i_size = ULONGLONG(inode_buf + CU_OFFSET(inode_i_size));
	if (nrpages)
		if (!readmem(*i_mapping + OFFSET(address_space_nrpages),
		    KVADDR, nrpages, sizeof(ulong), "i_mapping.nrpages",
		    RETURN_ON_ERROR))
			goto bail_out;

	ret = TRUE;
bail_out:
	FREEBUF(inode_buf);
	return ret;
}

static ulong *
get_subdirs_list(int *cntptr, ulong dentry)
{
	struct list_data list_data, *ld;
	ulong d_subdirs, child;

	d_subdirs = dentry + CU_OFFSET(dentry_d_subdirs);

	if (!readmem(d_subdirs, KVADDR, &child, sizeof(ulong),
	    "dentry.d_subdirs", RETURN_ON_ERROR))
		return NULL;

	if (d_subdirs == child)
		return NULL;

	ld = &list_data;
	BZERO(ld, sizeof(struct list_data));
	ld->flags |= (LIST_ALLOCATE|RETURN_ON_LIST_ERROR);
	ld->start = child;
	ld->end = d_subdirs;
	ld->list_head_offset = CU_OFFSET(dentry_d_child);
	if (CRASHDEBUG(3))
		ld->flags |= VERBOSE;

	if ((*cntptr = do_list(ld)) == -1)
		return NULL;

	return ld->list_ptr;
}

static char *
get_type_indicator(uint i_mode)
{
	static char c[2] = {'\0', '\0'};

	if (S_ISREG(i_mode)) {
		if (i_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
			*c = '*';
		else
			*c = '\0';
	} else if (S_ISDIR(i_mode))
		*c = '/';
	else if (S_ISLNK(i_mode))
		*c = '@';
	else if (S_ISFIFO(i_mode))
		*c = '|';
	else if (S_ISSOCK(i_mode))
		*c = '=';
	else
		*c = '\0';

	return c;
}

static ulonglong
byte_to_page(ulonglong i_size)
{
	return (i_size / PAGESIZE()) + ((i_size % PAGESIZE()) ? 1 : 0);
}

static int
calc_cached_percent(ulong nrpages, ulonglong i_size)
{
	if (!i_size)
		return 0;

	return (nrpages * 100) / byte_to_page(i_size);
}

typedef struct {
	ulong dentry;
	char *name;
	ulong inode;
	ulong i_mapping;
	ulonglong i_size;
	ulong nrpages;
	uint i_mode;
} inode_info_t;

static int
sort_by_name(const void *arg1, const void *arg2)
{
	inode_info_t *p = (inode_info_t *)arg1;
	inode_info_t *q = (inode_info_t *)arg2;

	return strcmp(p->name, q->name);
}

static void
show_subdirs_info(ulong dentry)
{
	ulong *list;
	int i, count;
	char *dentry_buf;
	ulong d, inode, i_mapping, nrpages;
	uint i_mode;
	ulonglong i_size;
	inode_info_t *inode_list, *p;

	if (!(list = get_subdirs_list(&count, dentry)))
		return;

	inode_list = (inode_info_t *)GETBUF(sizeof(inode_info_t) * count);

	for (i = 0, p = inode_list; i < count; i++) {
		d = list[i];
		dentry_buf = fill_dentry_cache(d);

		inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));
		if (inode && get_inode_info(inode, &i_mode, &i_mapping,
					&i_size, &nrpages)) {
			p->inode = inode;
			p->i_mapping = i_mapping;
			p->i_size = i_size;
			p->nrpages = nrpages;
			p->i_mode = i_mode;
		} else {
			p->i_mapping = 0;
			if (!(flags & SHOW_INFO_NEG_DENTS))
				continue;
		}
		p->dentry = d;
		p->name = strdup(get_dentry_name(d, dentry_buf));
		p++;
	}
	count = p - inode_list;

	if (!(flags & SHOW_INFO_DONT_SORT))
		qsort(inode_list, count, sizeof(inode_info_t), sort_by_name);

	for (i = 0, p = inode_list; i < count; i++, p++) {
		if (p->i_mapping) {
			int pct = calc_cached_percent(p->nrpages, p->i_size);
			
			fprintf(fp, dentry_fmt, p->dentry, p->inode,
				p->i_mapping, p->nrpages, pct, p->name,
				get_type_indicator(p->i_mode));

			if (CRASHDEBUG(1))
				fprintf(fp, "  i_mode:%6o i_size:%llu (%llu)\n",
					p->i_mode, p->i_size,
					byte_to_page(p->i_size));

		} else if (flags & SHOW_INFO_NEG_DENTS)
			fprintf(fp, negdent_fmt,
				p->dentry, "-", "-", "-", "-", p->name);
		free(p->name);
	}

	FREEBUF(inode_list);
	FREEBUF(list);
}

#ifdef NOT_EXPORTED
static 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");
		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);
}
#endif /* NOT_EXPORTED */

static ulong
get_mntpoint_dentry(char *path, char **remaining_path)
{
	ulong pid, *mount_list;
	int i, count;
	size_t len;
	struct task_context *nscon;
	char *mount_buf, *path_buf, *path_start, *slash_pos;
	char buf[BUFSIZE], *bufp = buf;
	ulong root, parent, mountp;

	pid = 0;
	while ((nscon = pid_to_context(pid)) == NULL)
		pid++;

	mount_list = get_mount_list(&count, nscon);

	mount_buf = VALID_STRUCT(mount) ? GETBUF(SIZE(mount)) :
			GETBUF(SIZE(vfsmount));

	len = strlen(path);
	path_buf = GETBUF(len + 1);
	memcpy(path_buf, path, len + 1);

	path_start = path + len;

	root = 0;
	while (TRUE) {
		for (i = 0; i < count; i++) {
			if (!readmem(mount_list[i], KVADDR, mount_buf,
			    VALID_STRUCT(mount) ? SIZE(mount) : SIZE(vfsmount),
			    "(vfs)mount buffer", RETURN_ON_ERROR))
				goto bail_out;

			if (VALID_STRUCT(mount)) {
				parent = ULONG(mount_buf +
					OFFSET(mount_mnt_parent));
				mountp = ULONG(mount_buf +
					OFFSET(mount_mnt_mountpoint));
				get_pathname(mountp, bufp, BUFSIZE, 1,
					parent + OFFSET(mount_mnt));
			} else {
				parent = ULONG(mount_buf +
					OFFSET(vfsmount_mnt_parent));
				mountp = ULONG(mount_buf +
					OFFSET(vfsmount_mnt_mountpoint));
				get_pathname(mountp, bufp, BUFSIZE, 1,
					parent);
			}

			if (CRASHDEBUG(2))
				error(INFO, "path:%s PATHEQ:%d mntp:%s\n",
					path_buf, PATHEQ(path_buf, bufp), bufp);

			if (PATHEQ(path_buf, bufp)) {
				if (VALID_STRUCT(mount))
					root = ULONG(mount_buf +
						OFFSET(mount_mnt) +
						CU_OFFSET(vfsmount_mnt_root));
				else
					root = ULONG(mount_buf +
						CU_OFFSET(vfsmount_mnt_root));
				/*
				 * Probably the last one will be what we want,
				 * so don't break here.
				 */
			}
		}
		if (root)
			break;

		if ((slash_pos = strrchr(path_buf, '/')) == NULL)
			break;

		path_start = path + (slash_pos - path_buf) + 1;

		if (slash_pos != path_buf)
			*slash_pos = '\0';
		else if (slash_pos == path_buf && *(slash_pos+1) != '\0')
			*(slash_pos+1) = '\0';
		else
			break;
	}
	if (CRASHDEBUG(2))
		error(INFO, "root_dentry:%lx path_start:%s\n",
			root, path_start);

	if (root && remaining_path)
		*remaining_path = path_start;

bail_out:
	FREEBUF(path_buf);
	FREEBUF(mount_buf);
	FREEBUF(mount_list);

	return root;
}

static ulong
path_to_dentry(char *path, ulong *inode)
{
	int i, count;
	ulong *subdirs_list, root, d, dentry;
	char *path_buf, *dentry_buf, *slash_pos, *path_start, *name;

	root = get_mntpoint_dentry(path, &path_start);
	if (!root) {
		error(INFO, "%s: mount point not found\n", path);
		return 0;
	}

	path_buf = GETBUF(strlen(path_start) + 1);
	memcpy(path_buf, path_start, strlen(path_start) + 1);
	path_start = path_buf;

	dentry = 0;
	dentry_buf = GETBUF(SIZE(dentry));
	d = root;

	while (strlen(path_start)) {
		if ((slash_pos = strchr(path_start, '/')))
			*slash_pos = '\0';

		if (!(subdirs_list = get_subdirs_list(&count, d)))
			goto not_found;

		for (i = 0; i < count; i++) {
			d = subdirs_list[i];
			if (!readmem(d, KVADDR, dentry_buf, SIZE(dentry),
			    "dentry buffer", RETURN_ON_ERROR))
				continue;

			name = get_dentry_name(d, dentry_buf);

			if (CRASHDEBUG(2))
				error(INFO, "q:%s %3d: d:%lx name:%s\n",
					path_start, i, d, name);

			if (STREQ(path_start, name)) {
				if (slash_pos)
					break;
				else {
					FREEBUF(subdirs_list);
					goto found;
				}
			}
		}
		FREEBUF(subdirs_list);

		/* no such dentry */
		if (i == count)
			goto not_found;

		if (slash_pos)
			path_start = slash_pos + 1;
	}
	/* the path ends with '/' */
	if (!readmem(d, KVADDR, dentry_buf, SIZE(dentry),
	    "dentry buffer", RETURN_ON_ERROR))
		goto not_found;

found:
	dentry = d;
	if (inode)
		*inode = ULONG(dentry_buf + OFFSET(dentry_d_inode));

not_found:
	FREEBUF(dentry_buf);
	FREEBUF(path_buf);

	return dentry;
}

/*
 * Currently just squeeze a series of slashes into a slash,
 * and remove the last slash.
 */
static void
normalize_path(char *path)
{
	char *s, *d;

	if (!path || *path == '\0')
		return;
	
	s = d = path;
	while (*s) {
		if (*s == '/' && *(s+1) == '/') {
			s++;
			continue;
		}
		*d++ = *s++;
	}
	*d = '\0';

	d--;
	if (d != path && *d == '/')
		*d = '\0';
}

static void
do_command(char *arg)
{
	ulong inode, i_mapping, root, dentry;
	struct list_pair lp;
	ulong count, nrpages;
	uint i_mode;

	inode = dentry = 0;
	if (flags & DUMP_CACHES)
		inode = htol(arg, RETURN_ON_ERROR|QUIET, NULL);

	if (flags & SHOW_INFO || inode == BADADDR) {
		if (arg[0] != '/')
			cmd_usage(pc->curcmd, SYNOPSIS);

		normalize_path(arg);

		dentry = path_to_dentry(arg, &inode);
		if (!dentry) {
			error(INFO, "%s: not found in dentry cache\n", arg);
			return;
		} else if (!inode) {
			error(INFO, "%s: negative dentry\n", arg);
			return;
		}
	}

	if (!get_inode_info(inode, &i_mode, &i_mapping, &i_size, &nrpages))
		return;

	if (flags & DUMP_CACHES) {
		if (!S_ISREG(i_mode)) {
			error(INFO, "%s: not regular file\n", arg);
			return;
		} else if (!nrpages) {
			error(INFO, "%s: no page caches\n", arg);
			return;
		}

		root = i_mapping + OFFSET(address_space_page_tree);
		lp.value = dump_slot;
		nr_written = nr_excluded = 0;

		pgbuf = GETBUF(PAGESIZE());

		if (MEMBER_EXISTS("address_space", "i_pages") &&
		    STREQ(MEMBER_TYPE_NAME("address_space", "i_pages"), "xarray"))
			count = do_xarray(root, XARRAY_DUMP_CB, &lp);
		else
			count = do_radix_tree(root, RADIX_TREE_DUMP_CB, &lp);

		FREEBUF(pgbuf);

		if (!(flags & DUMP_DONT_SEEK))
			ftruncate(fileno(outfp), i_size);

		if (nr_excluded)
			error(INFO, "%lu/%lu pages excluded\n",
				nr_excluded, count);
		if (CRASHDEBUG(1))
			error(INFO, "%lu/%lu pages written\n",
				nr_written, count);

	} else if (flags & SHOW_INFO) {
		fprintf(fp, header_fmt, "DENTRY", "INODE", "I_MAPPING",
			"NRPAGES", "%", "PATH");

		int pct = calc_cached_percent(nrpages, i_size);

		if (flags & SHOW_INFO_DIRS || !S_ISDIR(i_mode)) {
			fprintf(fp, dentry_fmt, dentry, inode, i_mapping,
				nrpages, pct, arg, get_type_indicator(i_mode));
			if (CRASHDEBUG(1))
				fprintf(fp, "  i_mode:%6o i_size:%llu (%llu)\n",
					i_mode, i_size, byte_to_page(i_size));
		} else {
			fprintf(fp, dentry_fmt, dentry, inode, i_mapping,
				nrpages, pct, ".", get_type_indicator(i_mode));
			if (CRASHDEBUG(1))
				fprintf(fp, "  i_mode:%6o i_size:%llu (%llu)\n",
					i_mode, i_size, byte_to_page(i_size));
			show_subdirs_info(dentry);
		}
	}
}

void
cmd_ccat(void)
{
	int c;
	char *arg;

	flags = DUMP_CACHES;

	while ((c = getopt(argcnt, args, "S")) != EOF) {
		switch(c) {
		case 'S':
			flags |= DUMP_DONT_SEEK;
			break;
		default:
			argerrs++;
			break;
		}
	}

	if (argerrs || !args[optind])
		cmd_usage(pc->curcmd, SYNOPSIS);

	arg = args[optind++];

	if (args[optind]) {
		if (access(args[optind], F_OK) == 0) {
			error(INFO, "%s: %s\n",
				args[optind], strerror(EEXIST));
			return;
		}
		if ((outfp = fopen(args[optind], "w")) == NULL) {
			error(INFO, "cannot open %s: %s\n",
				args[optind], strerror(errno));
			return;
		}
		set_tmpfile2(outfp);
	} else
		outfp = fp;

	do_command(arg);

	if (outfp != fp)
		close_tmpfile2();
}

char *help_ccat[] = {
"ccat",				/* command name */
"dump page caches",		/* short description */
"[-S] inode|abspath [outfile]",	/* argument synopsis, or " " if none */

"  This command dumps the page caches of a specified inode or path like",
"  \"cat\" command.",
"",
"       -S  do not fseek() and ftruncate() to outfile in order to",
"           create a non-sparse file.",
"    inode  a hexadecimal inode pointer.",
"  abspath  an absolute path.",
"  outfile  a file path to be written. If a file already exists there,",
"           the command fails.",
"",
"EXAMPLE",
"  Dump the existing page caches of the \"/var/log/messages\" file:",
"",
"    %s> ccat /var/log/messages",
"    Sep 16 03:13:01 host systemd: Started Session 559694 of user root.",
"    Sep 16 03:13:01 host systemd: Starting Session 559694 of user root.",
"    Sep 16 03:13:39 host dnsmasq-dhcp[24341]: DHCPREQUEST(virbr0) 192.168",
"    Sep 16 03:13:39 host dnsmasq-dhcp[24341]: DHCPACK(virbr0) 192.168.122",
"    ...",
"",
"  Restore the size and data offset of the \"messages\" file as well to the",
"  \"messages.sparse\" file even if some of its page caches don't exist, so",
"  it could become sparse:",
"",
"    %s> ccat /var/log/messages messages.sparse",
"",
"  Create the non-sparse \"messages.non-sparse\" file:",
"",
"    %s> ccat -S /var/log/messages messages.non-sparse",
"",
"  NOTE: Redirecting to a file will also works, but it can includes crash's",
"  messages, so specifying an outfile is recommended for restoring a file.",
NULL
};

void
cmd_cls(void)
{
	int c;

	flags = SHOW_INFO;

	while ((c = getopt(argcnt, args, "adU")) != EOF) {
		switch(c) {
		case 'a':
			flags |= SHOW_INFO_NEG_DENTS;
			break;
		case 'd':
			flags |= SHOW_INFO_DIRS;
			break;
		case 'U':
			flags |= SHOW_INFO_DONT_SORT;
			break;
		default:
			argerrs++;
			break;
		}
	}

	if (argerrs || !args[optind])
		cmd_usage(pc->curcmd, SYNOPSIS);

	do_command(args[optind++]);

	while (args[optind]) {
		fprintf(fp, "\n");
		do_command(args[optind++]);
	}
}

char *help_cls[] = {
"cls",				/* command name */
"list dentry and inode caches",	/* short description */
"[-adU] abspath...",		/* argument synopsis, or " " if none */

"  This command displays the addresses of dentry, inode and i_mapping,",
"  and nrpages of a specified absolute path and its subdirs if it exists",
"  in dentry cache.",
"",
"    -a  also display negative dentries in the subdirs list.",
"    -d  display the directory itself only, without its contents.",
"    -U  do not sort, list dentries in directory order.",
"",
"EXAMPLE",
"  Display the \"/var/log/messages\" regular file's information:",
"",
"    %s> cls /var/log/messages",
"    DENTRY           INODE            I_MAPPING        NRPAGES   % PATH",
"    ffff9c0c28fda480 ffff9c0c22c675b8 ffff9c0c22c67728     220 100 /var/log/messages",
"",
"  The '\%' column shows the percentage of cached pages in the file.",
"",
"  Display the \"/var/log\" directory and its subdirs information:",
"",
"    %s> cls /var/log",
"    DENTRY           INODE            I_MAPPING        NRPAGES   % PATH",
"    ffff9c0c3eabe300 ffff9c0c3e875b78 ffff9c0c3e875ce8       0   0 ./",
"    ffff9c0c16a22900 ffff9c0c16ada2f8 ffff9c0c16ada468       0   0 anaconda/",
"    ffff9c0c37611000 ffff9c0c3759f5b8 ffff9c0c3759f728       0   0 audit/",
"    ffff9c0c375ccc00 ffff9c0c3761c8b8 ffff9c0c3761ca28       1 100 btmp",
"    ffff9c0c28fda240 ffff9c0c22c713f8 ffff9c0c22c71568       6 100 cron",
"    ffff9c0c3eb7f180 ffff9c0bfd402a78 ffff9c0bfd402be8      36   7 dnf.librepo.log",
"    ...",
"",
"  Display the \"/var/log\" directory itself only:",
"",
"    %s> cls -d /var/log",
"    DENTRY           INODE            I_MAPPING        NRPAGES   % PATH",
"    ffff9c0c3eabe300 ffff9c0c3e875b78 ffff9c0c3e875ce8       0   0 /var/log/",
NULL
};

static struct command_table_entry command_table[] = {
	{ "ccat", cmd_ccat, help_ccat, 0},
	{ "cls", cmd_cls, help_cls, 0},
	{ NULL },
};

void __attribute__((constructor))
cacheutils_init(void)
{ 
	register_extension(command_table);

	CU_OFFSET_INIT(inode_i_size, "inode", "i_size");
	CU_OFFSET_INIT(vfsmount_mnt_root, "vfsmount", "mnt_root");
	CU_OFFSET_INIT(dentry_d_subdirs, "dentry", "d_subdirs");
	CU_OFFSET_INIT(dentry_d_child, "dentry", "d_child");
	if (CU_INVALID_MEMBER(dentry_d_child))	/* RHEL7 and older */
		CU_OFFSET_INIT(dentry_d_child, "dentry", "d_u");

	if (CRASHDEBUG(1)) {
		error(INFO, "       inode_i_size: %lu\n",
			CU_OFFSET(inode_i_size));
		error(INFO, "  vfsmount_mnt_root: %lu\n",
			CU_OFFSET(vfsmount_mnt_root));
		error(INFO, "   dentry_d_subdirs: %lu\n",
			CU_OFFSET(dentry_d_subdirs));
		error(INFO, "     dentry_d_child: %lu\n",
			CU_OFFSET(dentry_d_child));
	}

#ifndef NOT_EXPORTED
#define DL_EXCLUDE_CACHE_PRI	(0x04)
	int dump_level;
	if ((*diskdump_flags & KDUMP_CMPRS_LOCAL) &&
	    ((dump_level = get_dump_level()) >= 0) &&
	    (dump_level & DL_EXCLUDE_CACHE_PRI))
		error(WARNING, "\"ccat\" command is unusable because all of"
			" cache pages are excluded (dump_level:%d)\n",
			dump_level);
#endif
}
 
void __attribute__((destructor))
cacheutils_fini(void)
{
}
--
Crash-utility mailing list
Crash-utility@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/crash-utility

[Index of Archives]     [Fedora Development]     [Fedora Desktop]     [Fedora SELinux]     [Yosemite News]     [KDE Users]     [Fedora Tools]

 

Powered by Linux