Last update: 20-Oct-2019
Author: R. Koucha
Value of "%h" in "/proc/sys/kernel/core_pattern"
Introduction

According to the Linux online manual ("man 5 core"), the /proc/sys/kernel/core_pattern file can be set to define a template that is used to name core dump files.  The template can contain % specifiers which are substituted by the following values when a core file is created:

A single "%" at the end of the template is dropped from the core filename, as is the combination of a % followed by any character other than those listed above.

Moreover, if the first character is a "|" directly followed by a program's pathname, the latter is run in the initial namespaces (PID, mount, user and so on) and with the content of the core passed into its standard input. The preceding list of "%" specifiers can be used on the command line arguments of the program.  For example, on my Ubuntu system, The file contains:

$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P

The manual specifies that the  process  runs in the initial namespaces (PID, mount, user, and so on) and not in the namespaces of the crashing process.  One can utilize specifiers such as %P to find the right /proc/[pid] directory and probe/enter the crashing process's namespaces if needed.

But what about "%h" ? The manual merely says that it is the hostname. But which one ? The crashing process one or the initial one ?

Linux source code

In the Linux source code, the "%" parameters are substituted in "fs/coredump.c":

/* format_corename will inspect the pattern parameter, and output a
 * name into corename, which must have space for at least
 * CORENAME_MAX_SIZE bytes plus one byte for the zero terminator.
 */
static int format_corename(struct core_name *cn, struct coredump_params *cprm,
			   size_t **argv, int *argc)
{
	const struct cred *cred = current_cred();
	const char *pat_ptr = core_pattern;
	int ispipe = (*pat_ptr == '|');
	bool was_space = false;
	int pid_in_pattern = 0;
	int err = 0;

	cn->used = 0;
	cn->corename = NULL;
	if (expand_corename(cn, core_name_size))
		return -ENOMEM;
	cn->corename[0] = '\0';

	if (ispipe) {
		int argvs = sizeof(core_pattern) / 2;
		(*argv) = kmalloc_array(argvs, sizeof(**argv), GFP_KERNEL);
		if (!(*argv))
			return -ENOMEM;
		(*argv)[(*argc)++] = 0;
		++pat_ptr;
	}

	/* Repeat as long as we have more pattern to process and more output
	   space */
	while (*pat_ptr) {
		/*
		 * Split on spaces before doing template expansion so that
		 * %e and %E don't get split if they have spaces in them
		 */
		if (ispipe) {
			if (isspace(*pat_ptr)) {
				was_space = true;
				pat_ptr++;
				continue;
			} else if (was_space) {
				was_space = false;
				err = cn_printf(cn, "%c", '\0');
				if (err)
					return err;
				(*argv)[(*argc)++] = cn->used;
			}
		}
		if (*pat_ptr != '%') {
			err = cn_printf(cn, "%c", *pat_ptr++);
		} else {
			switch (*++pat_ptr) {
			/* single % at the end, drop that */
			case 0:
				goto out;
			/* Double percent, output one percent */
			case '%':
				err = cn_printf(cn, "%c", '%');
				break;
			/* pid */
			case 'p':
				pid_in_pattern = 1;
				err = cn_printf(cn, "%d",
					      task_tgid_vnr(current));
				break;
			/* global pid */
			case 'P':
				err = cn_printf(cn, "%d",
					      task_tgid_nr(current));
				break;
			case 'i':
				err = cn_printf(cn, "%d",
					      task_pid_vnr(current));
				break;
			case 'I':
				err = cn_printf(cn, "%d",
					      task_pid_nr(current));
				break;
			/* uid */
			case 'u':
				err = cn_printf(cn, "%u",
						from_kuid(&init_user_ns,
							  cred->uid));
				break;
			/* gid */
			case 'g':
				err = cn_printf(cn, "%u",
						from_kgid(&init_user_ns,
							  cred->gid));
				break;
			case 'd':
				err = cn_printf(cn, "%d",
					__get_dumpable(cprm->mm_flags));
				break;
			/* signal that caused the coredump */
			case 's':
				err = cn_printf(cn, "%d",
						cprm->siginfo->si_signo);
				break;
			/* UNIX time of coredump */
			case 't': {
				time64_t time;

				time = ktime_get_real_seconds();
				err = cn_printf(cn, "%lld", time);
				break;
			}
/* hostname */ case 'h': down_read(&uts_sem); err = cn_esc_printf(cn, "%s", utsname()->nodename); up_read(&uts_sem); break;
/* executable */ case 'e': err = cn_esc_printf(cn, "%s", current->comm); break; case 'E': err = cn_print_exe_file(cn); break; /* core limit size */ case 'c': err = cn_printf(cn, "%lu", rlimit(RLIMIT_CORE)); break; default: break; } ++pat_ptr; } if (err) return err; } out: /* Backward compatibility with core_uses_pid: * * If core_pattern does not include a %p (as is the default) * and core_uses_pid is set, then .%pid will be appended to * the filename. Do not do this for piped commands. */ if (!ispipe && !pid_in_pattern && core_uses_pid) { err = cn_printf(cn, ".%d", task_tgid_vnr(current)); if (err) return err; } return ispipe; }

We can see in the previous code that "%h" is substituted by "utsname()->nodename". The "utsname()" function is defined in "include/linux/utsname.h" as follow:

static inline struct new_utsname *utsname(void)
{
	return &current->nsproxy->uts_ns->name;
}

In other words, the host name is the one defined in the uts_namespace of the crashing task (it is not the host name of the initial namespace).


About the author

The author is an engineer in computer sciences located in France. He can be contacted here.