Viewing: jobid.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2011, 2014, Intel Corporation.
 *
 * Copyright 2017 Cray Inc, all rights reserved.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Store PID->JobID mappings
 *
 * Author: Ben Evans.
 */

#define DEBUG_SUBSYSTEM S_RPC

#include <linux/user_namespace.h>
#include <linux/uidgid.h>
#include <linux/utsname.h>

#include <cfs_hash.h>
#include <obd_support.h>
#include <obd_class.h>
#include <lustre_net.h>

static struct cfs_hash *jobid_hash;
static struct cfs_hash_ops jobid_hash_ops;
static spinlock_t jobid_hash_lock;

#define RESCAN_INTERVAL 30
#define DELETE_INTERVAL 300

char obd_jobid_var[JOBSTATS_JOBID_VAR_MAX_LEN + 1] = JOBSTATS_DISABLE;
char obd_jobid_name[LUSTRE_JOBID_SIZE] = "%e.%u";

/**
 * Structure to store a single PID->JobID mapping
 */
struct jobid_pid_map {
	struct hlist_node	jp_hash;
	time64_t		jp_time;
	spinlock_t		jp_lock; /* protects jp_jobid */
	char			jp_jobid[LUSTRE_JOBID_SIZE];
	unsigned int		jp_joblen;
	struct kref		jp_refcount;
	pid_t			jp_pid;
};

/*
 * Jobid can be set for a session (see setsid(2)) by writing to
 * a sysfs file from any process in that session.
 * The jobids are stored in a hash table indexed by the relevant
 * struct pid.  We periodically look for entries where the pid has
 * no PIDTYPE_SID tasks any more, and prune them.  This happens within
 * 5 seconds of a jobid being added, and every 5 minutes when jobids exist,
 * but none are added.
 */
#define JOBID_EXPEDITED_CLEAN (5)
#define JOBID_BACKGROUND_CLEAN (5 * 60)

struct session_jobid {
	struct pid		*sj_session;
	struct rhash_head	sj_linkage;
	struct rcu_head		sj_rcu;
	char			sj_jobid[];
};

static const struct rhashtable_params jobid_params = {
	.key_len	= sizeof(struct pid *),
	.key_offset	= offsetof(struct session_jobid, sj_session),
	.head_offset	= offsetof(struct session_jobid, sj_linkage),
};

static struct rhashtable session_jobids;

/*
 * jobid_current must be called with rcu_read_lock held.
 * if it returns non-NULL, the string can only be used
 * until rcu_read_unlock is called.
 */
char *jobid_current(void)
{
	struct pid *sid = task_session(current);
	struct session_jobid *sj;

	sj = rhashtable_lookup_fast(&session_jobids, &sid, jobid_params);
	if (sj)
		return sj->sj_jobid;
	return NULL;
}

static void jobid_prune_expedite(void);
/*
 * jobid_set_current will try to add a new entry
 * to the table.  If one exists with the same key, the
 * jobid will be replaced
 */
int jobid_set_current(char *jobid)
{
	struct pid *sid;
	struct session_jobid *sj, *origsj;
	int ret;
	int len = strlen(jobid);

	OBD_ALLOC(sj, sizeof(*sj) + len + 1);
	if (!sj)
		return -ENOMEM;
	rcu_read_lock();
	sid = task_session(current);
	sj->sj_session = get_pid(sid);
	strncpy(sj->sj_jobid, jobid, len + 1);
	origsj = rhashtable_lookup_get_insert_fast(&session_jobids,
						   &sj->sj_linkage,
						   jobid_params);
	if (origsj == NULL) {
		/* successful insert */
		rcu_read_unlock();
		jobid_prune_expedite();
		return 0;
	}

	if (IS_ERR(origsj)) {
		put_pid(sj->sj_session);
		OBD_FREE(sj, sizeof(*sj) + strlen(sj->sj_jobid) + 1);
		rcu_read_unlock();
		return PTR_ERR(origsj);
	}
	ret = rhashtable_replace_fast(&session_jobids,
				      &origsj->sj_linkage,
				      &sj->sj_linkage,
				      jobid_params);
	if (ret) {
		put_pid(sj->sj_session);
		OBD_FREE(sj, sizeof(*sj) + strlen(sj->sj_jobid) + 1);
		rcu_read_unlock();
		return ret;
	}
	put_pid(origsj->sj_session);
	rcu_read_unlock();
	OBD_FREE_RCU(origsj, sizeof(*sj) + strlen(origsj->sj_jobid) + 1, sj_rcu);
	jobid_prune_expedite();

	return 0;
}

static void jobid_free(void *vsj, void *arg)
{
	struct session_jobid *sj = vsj;

	put_pid(sj->sj_session);
	OBD_FREE(sj, sizeof(*sj) + strlen(sj->sj_jobid) + 1);;
}

static void jobid_prune(struct work_struct *work);
static DECLARE_DELAYED_WORK(jobid_prune_work, jobid_prune);
static int jobid_prune_expedited;
static void jobid_prune(struct work_struct *work)
{
	int remaining = 0;
	struct rhashtable_iter iter;
	struct session_jobid *sj;

	jobid_prune_expedited = 0;
	rhashtable_walk_enter(&session_jobids, &iter);
	rhashtable_walk_start(&iter);
	while ((sj = rhashtable_walk_next(&iter)) != NULL) {
		if (IS_ERR(sj)) {
			if (PTR_ERR(sj) == -EAGAIN)
				continue;
			break;
		}
		if (!hlist_empty(&sj->sj_session->tasks[PIDTYPE_SID])) {
			remaining++;
			continue;
		}
		if (rhashtable_remove_fast(&session_jobids,
					   &sj->sj_linkage,
					   jobid_params) == 0) {
			put_pid(sj->sj_session);
			OBD_FREE_RCU(sj, sizeof(*sj) + strlen(sj->sj_jobid) + 1,
				     sj_rcu);
		}
	}
	rhashtable_walk_stop(&iter);
	rhashtable_walk_exit(&iter);
	if (remaining)
		schedule_delayed_work(&jobid_prune_work,
				      cfs_time_seconds(JOBID_BACKGROUND_CLEAN));
}

static void jobid_prune_expedite(void)
{
	/* submit the work only once */
	if (!cmpxchg(&jobid_prune_expedited, 0, 1))
		mod_delayed_work(system_wq, &jobid_prune_work,
				 cfs_time_seconds(JOBID_EXPEDITED_CLEAN));
}

static int cfs_access_process_vm(struct task_struct *tsk,
				 struct mm_struct *mm,
				 unsigned long addr,
				 void *buf, int len, int write)
{
	/* Just copied from kernel for the kernels which doesn't
	 * have access_process_vm() exported
	 */
	struct vm_area_struct *vma = NULL;
	struct page *page;
	void *old_buf = buf;

	/* Avoid deadlocks on mmap_sem if called from sys_mmap_pgoff(),
	 * which is already holding mmap_sem for writes.  If some other
	 * thread gets the write lock in the meantime, this thread will
	 * block, but at least it won't deadlock on itself.  LU-1735
	 */
	if (!mmap_read_trylock(mm))
		return -EDEADLK;

	/* ignore errors, just check how much was successfully transferred */
	while (len) {
		int bytes, rc, offset;
		void *maddr;

#if defined(HAVE_GET_USER_PAGES_WITHOUT_VMA)
		rc = get_user_pages(addr, 1, write ? FOLL_WRITE : 0, &page);
		if (rc > 0)
			vma = vma_lookup(mm, addr);
#else
		rc = get_user_pages(addr, 1, write ? FOLL_WRITE : 0, &page,
				    &vma);
#endif
		if (rc <= 0 || !vma)
			break;

		bytes = len;
		offset = addr & (PAGE_SIZE-1);
		if (bytes > PAGE_SIZE-offset)
			bytes = PAGE_SIZE-offset;

		maddr = kmap(page);
		if (write) {
			copy_to_user_page(vma, page, addr,
					  maddr + offset, buf, bytes);
			set_page_dirty_lock(page);
		} else {
			copy_from_user_page(vma, page, addr,
					    buf, maddr + offset, bytes);
		}
		kunmap(kmap_to_page(maddr));
		put_page(page);
		len -= bytes;
		buf += bytes;
		addr += bytes;
	}
	mmap_read_unlock(mm);

	return buf - old_buf;
}

/* Read the environment variable of current process specified by @key. */
static int cfs_get_environ(const char *key, char *value, int *val_len)
{
	struct mm_struct *mm;
	char *buffer;
	int buf_len = PAGE_SIZE;
	int key_len = strlen(key);
	unsigned long addr;
	int rc;
	bool skip = false;

	ENTRY;
	buffer = kmalloc(buf_len, GFP_USER);
	if (!buffer)
		RETURN(-ENOMEM);

	mm = get_task_mm(current);
	if (!mm) {
		kfree(buffer);
		RETURN(-EINVAL);
	}

	addr = mm->env_start;
	while (addr < mm->env_end) {
		int this_len, retval, scan_len;
		char *env_start, *env_end;

		memset(buffer, 0, buf_len);

		this_len = min_t(int, mm->env_end - addr, buf_len);
		retval = cfs_access_process_vm(current, mm, addr, buffer,
					       this_len, 0);
		if (retval < 0)
			GOTO(out, rc = retval);
		else if (retval != this_len)
			break;

		addr += retval;

		/* Parse the buffer to find out the specified key/value pair.
		 * The "key=value" entries are separated by '\0'.
		 */
		env_start = buffer;
		scan_len = this_len;
		while (scan_len) {
			char *entry;
			int entry_len;

			env_end = memscan(env_start, '\0', scan_len);
			LASSERT(env_end >= env_start &&
				env_end <= env_start + scan_len);

			/* The last entry of this buffer cross the buffer
			 * boundary, reread it in next cycle.
			 */
			if (unlikely(env_end - env_start == scan_len)) {
				/* Just skip the entry larger than page size,
				 * it can't be jobID env variable.
				 */
				if (unlikely(scan_len == this_len))
					skip = true;
				else
					addr -= scan_len;
				break;
			} else if (unlikely(skip)) {
				skip = false;
				goto skip;
			}
			entry = env_start;
			entry_len = env_end - env_start;
			CDEBUG(D_INFO, "key: %s, entry: %s\n", key, entry);

			/* Key length + length of '=' */
			if (entry_len > key_len + 1 &&
			    entry[key_len] == '='  &&
			    !memcmp(entry, key, key_len)) {
				entry += key_len + 1;
				entry_len -= key_len + 1;

				/* The 'value' buffer passed in is too small.
				 * Copy what fits, but return -EOVERFLOW.
				 */
				if (entry_len >= *val_len) {
					memcpy(value, entry, *val_len);
					value[*val_len - 1] = 0;
					GOTO(out, rc = -EOVERFLOW);
				}

				memcpy(value, entry, entry_len);
				*val_len = entry_len;
				GOTO(out, rc = 0);
			}
skip:
			scan_len -= (env_end - env_start + 1);
			env_start = env_end + 1;
		}
	}
	GOTO(out, rc = -ENOENT);

out:
	mmput(mm);
	kfree((void *)buffer);
	return rc;
}

/*
 * Get jobid of current process by reading the environment variable
 * stored in between the "env_start" & "env_end" of task struct.
 *
 * If some job scheduler doesn't store jobid in the "env_start/end",
 * then an upcall could be issued here to get the jobid by utilizing
 * the userspace tools/API. Then, the jobid must be cached.
 */
static int jobid_get_from_environ(char *jobid_var, char *jobid, int *jobid_len)
{
	int rc;

	rc = cfs_get_environ(jobid_var, jobid, jobid_len);
	if (!rc)
		goto out;

	if (rc == -EOVERFLOW) {
		/* For the PBS_JOBID and LOADL_STEP_ID keys (which are
		 * variable length strings instead of just numbers), it
		 * might make sense to keep the unique parts for JobID,
		 * instead of just returning an error.  That means a
		 * larger temp buffer for cfs_get_environ(), then
		 * truncating the string at some separator to fit into
		 * the specified jobid_len.  Fix later if needed. */
		static ktime_t printed;

		if (unlikely(ktime_to_ns(printed) == 0 ||
			     ktime_after(ktime_get(),
					 ktime_add_ns(printed,
					     3600ULL * 24 * NSEC_PER_SEC)))) {
			LCONSOLE_WARN("jobid: '%s' value too large (%d)\n",
				      obd_jobid_var, *jobid_len);
			printed = ktime_get();
		}

		rc = 0;
	} else {
		CDEBUG_LIMIT((rc == -ENOENT || rc == -EINVAL ||
			      rc == -EDEADLK) ? D_INFO : D_ERROR,
			     "jobid: get '%s' failed: rc = %d\n",
			     obd_jobid_var, rc);
	}

out:
	return rc;
}

/*
 * jobid_should_free_item
 *
 * Each item is checked to see if it should be released
 * Removed from hash table by caller
 * Actually freed in jobid_put_locked
 *
 * Returns 1 if item is to be freed, 0 if it is to be kept
 */

static int jobid_should_free_item(void *obj, void *data)
{
	char *jobid = data;
	struct jobid_pid_map *pidmap = obj;
	int rc = 0;

	if (obj == NULL)
		return 0;

	if (jobid == NULL) {
		WARN_ON_ONCE(kref_read(&pidmap->jp_refcount) != 1);
		return 1;
	}

	spin_lock(&pidmap->jp_lock);
	/* prevent newly inserted items from deleting */
	if (jobid[0] == '\0' && kref_read(&pidmap->jp_refcount) == 1)
		rc = 1;
	else if (ktime_get_real_seconds() - pidmap->jp_time > DELETE_INTERVAL)
		rc = 1;
	else if (strcmp(pidmap->jp_jobid, jobid) == 0)
		rc = 1;
	spin_unlock(&pidmap->jp_lock);

	return rc;
}

static void jobid_pidmap_gc(struct work_struct *work);
static DECLARE_DELAYED_WORK(jobid_pidmap_gc_work, jobid_pidmap_gc);
static int jobid_pidmap_gc_started;

static void jobid_pidmap_gc(struct work_struct *work)
{
	struct cfs_hash *hash;

	hash = cfs_hash_getref(jobid_hash);
	if (!hash)
		return;

	CDEBUG(D_INFO, "jobid: running the PID map GC (count: %d)\n",
	       atomic_read(&jobid_hash->hs_count));

	cfs_hash_cond_del(jobid_hash, jobid_should_free_item,
			  "intentionally_bad_jobid");

	if (atomic_read(&jobid_hash->hs_count) == 0)
		jobid_pidmap_gc_started = 0;
	else
		schedule_delayed_work(&jobid_pidmap_gc_work,
				      cfs_time_seconds(DELETE_INTERVAL));

	cfs_hash_putref(hash);
}

/* scan hash periodically to remove old PID entries from cache */
static inline void jobid_pidmap_gc_start(void)
{
	/* submit the work only once */
	if (!cmpxchg(&jobid_pidmap_gc_started, 0, 1))
		schedule_delayed_work(&jobid_pidmap_gc_work,
				      cfs_time_seconds(DELETE_INTERVAL));
}


/*
 * jobid_name_is_valid
 *
 * Checks if the jobid is a Lustre process
 *
 * Returns true if jobid is valid
 * Returns false if jobid looks like it's a Lustre process
 */
static bool jobid_name_is_valid(char *jobid)
{
	const char *const lustre_reserved[] = { "ll_ping", "ptlrpc",
						"ldlm", "ll_sa", "kworker",
						"kswapd", "writeback", "irq",
						"ksoftirq", "ll_ucp", NULL };
	int i;

	if (jobid[0] == '\0')
		return false;

	for (i = 0; lustre_reserved[i] != NULL; i++) {
		if (strncmp(jobid, lustre_reserved[i],
			    strlen(lustre_reserved[i])) == 0)
			return false;
	}
	return true;
}

/*
 * jobid_get_from_cache()
 *
 * Returns contents of jobid_var from process environment for current PID,
 * or from the per-session jobid table.
 * Values fetch from process environment will be cached for some time to avoid
 * the overhead of scanning the environment.
 *
 * Return: -ENOMEM if allocating a new pidmap fails
 *         -ENOENT if no entry could be found
 *         +ve string length for success (something was returned in jobid)
 */
static int jobid_get_from_cache(char *jobid, size_t joblen)
{
	pid_t pid = current->pid;
	struct jobid_pid_map *pidmap = NULL;
	time64_t now = ktime_get_real_seconds();
	int rc = 0;
	ENTRY;

	if (strcmp(obd_jobid_var, JOBSTATS_SESSION) == 0) {
		char *jid;

		rcu_read_lock();
		jid = jobid_current();
		if (jid) {
			strscpy(jobid, jid, joblen);
			joblen = strlen(jobid);
		} else {
			rc = -ENOENT;
		}
		rcu_read_unlock();
		GOTO(out, rc);
	}

	LASSERT(jobid_hash != NULL);

	/* first try to find PID in the hash and use that value */
	pidmap = cfs_hash_lookup(jobid_hash, &pid);
	if (pidmap == NULL) {
		struct jobid_pid_map *pidmap2;

		OBD_ALLOC_PTR(pidmap);
		if (pidmap == NULL)
			GOTO(out, rc = -ENOMEM);

		pidmap->jp_pid = pid;
		pidmap->jp_time = 0;
		pidmap->jp_jobid[0] = '\0';
		spin_lock_init(&pidmap->jp_lock);
		INIT_HLIST_NODE(&pidmap->jp_hash);
		/*
		 * @pidmap might be reclaimed just after it is added into
		 * hash list, init @jp_refcount as 1 to make sure memory
		 * could be not freed during access.
		 */
		kref_init(&pidmap->jp_refcount);

		/*
		 * Add the newly created map to the hash, on key collision we
		 * lost a racing addition and must destroy our newly allocated
		 * map.  The object which exists in the hash will be returned.
		 */
		pidmap2 = cfs_hash_findadd_unique(jobid_hash, &pid,
						  &pidmap->jp_hash);
		if (unlikely(pidmap != pidmap2)) {
			CDEBUG(D_INFO, "jobid: duplicate found for PID=%u\n",
			       pid);
			OBD_FREE_PTR(pidmap);
			pidmap = pidmap2;
		} else {
			jobid_pidmap_gc_start();
		}
	}

	/*
	 * If pidmap is old (this is always true for new entries) refresh it.
	 * If obd_jobid_var is not found, cache empty entry and try again
	 * later, to avoid repeat lookups for PID if obd_jobid_var missing.
	 */
	spin_lock(&pidmap->jp_lock);
	if (pidmap->jp_time + RESCAN_INTERVAL <= now) {
		char env_jobid[LUSTRE_JOBID_SIZE] = "";
		int env_len = sizeof(env_jobid);

		pidmap->jp_time = now;

		spin_unlock(&pidmap->jp_lock);
		rc = jobid_get_from_environ(obd_jobid_var, env_jobid, &env_len);

		CDEBUG(D_INFO, "jobid: PID mapping established: %d->%s\n",
		       pidmap->jp_pid, env_jobid);
		spin_lock(&pidmap->jp_lock);
		if (!rc) {
			pidmap->jp_joblen = env_len;
			strscpy(pidmap->jp_jobid, env_jobid,
				sizeof(pidmap->jp_jobid));
			rc = 0;
		} else if (rc == -ENOENT) {
			/* It might have been deleted, clear out old entry */
			pidmap->jp_joblen = 0;
			pidmap->jp_jobid[0] = '\0';
		}
	}

	/*
	 * Regardless of how pidmap was found, if it contains a valid entry
	 * use that for now.  If there was a technical error (e.g. -ENOMEM)
	 * use the old cached value until it can be looked up again properly.
	 * If a cached missing entry was found, return -ENOENT.
	 */
	if (pidmap->jp_joblen) {
		strscpy(jobid, pidmap->jp_jobid, joblen);
		joblen = pidmap->jp_joblen;
		rc = 0;
	} else if (!rc) {
		rc = -ENOENT;
	}
	spin_unlock(&pidmap->jp_lock);

	cfs_hash_put(jobid_hash, &pidmap->jp_hash);

	EXIT;
out:
	return rc < 0 ? rc : joblen;
}

/*
 * jobid_print_current_comm()
 *
 * Print current comm name into the provided jobid buffer, and trim names of
 * kernel threads like "kworker/0:0" to "kworker" or "ll_sa_12345" to "ll_sa"
 *
 * Return: number of chars printed to jobid
 */
static int jobid_print_current_comm(char *jobid, ssize_t joblen)
{
	const char *const names[] = {"kworker", "kswapd", "ll_sa", "ll_agl",
				     "ldlm_bl", "ll_ucp", NULL};
	int i;

	if (current->flags & PF_KTHREAD) {
		for (i = 0; names[i] != NULL; i++) {
			if (strncmp(current->comm, names[i],
				    strlen(names[i])) == 0)
				return snprintf(jobid, joblen, "%s", names[i]);
		}
	}

	return snprintf(jobid, joblen, "%s", current->comm);
}

/*
 * jobid_interpret_string()
 *
 * Interpret the jobfmt string to expand specified fields, like coredumps do:
 *   %e = executable
 *   %g = gid
 *   %h = hostname
 *   %H = short hostname
 *   %j = jobid from environment
 *   ?  = for use between %j and %H. Uses jobid if set, otherwise use Hostname
 *   %p = pid
 *   %u = uid
 *
 * Truncation can also be interpreted by writing .n between % and field, for
 * example %.3h to print only the 3 first characaters.
 *
 * Unknown escape strings are dropped.  Other characters are copied through,
 * excluding whitespace (to avoid making jobid parsing difficult).
 *
 * Return: -EOVERFLOW if the expanded string does not fit within @joblen
 *         0 for success
 */
static int jobid_interpret_string(const char *jobfmt, char *jobid,
				  ssize_t joblen)
{
	char c;

	while ((c = *jobfmt++) && joblen > 1) {
		long width = joblen;
		char *p;
		int l;

		if (isspace(c)) /* Don't allow embedded spaces */
			continue;

		if (c != '%') {
			*jobid = c;
			joblen--;
			jobid++;
			*jobid = '\0';
			continue;
		}

		if (*jobfmt == '.') {
			long w = 0;
			int size = 0;

			jobfmt++;
			if (sscanf(jobfmt, "%ld%n", &w, &size) == 1)
				jobfmt += size;
			if (w > 0)
				width = min(w+1, joblen);
		}

		switch (*jobfmt++) {
		case 'e': /* executable name */
			l = jobid_print_current_comm(jobid, width);
			break;
		case 'g': /* group ID */
			l = snprintf(jobid, width, "%u",
				     from_kgid(&init_user_ns, current_fsgid()));
			break;
		case 'h': /* hostname */
			l = snprintf(jobid, width, "%s",
				     init_utsname()->nodename);
			break;
		case 'H': /* short hostname. Cut at first dot */
			l = snprintf(jobid, width, "%s",
				     init_utsname()->nodename);
			p = strnchr(jobid, width, '.');
			if (p) {
				*p = '\0';
				l = p - jobid;
			}
			break;
		case 'j': /* jobid stored in process environment */
			l = jobid_get_from_cache(jobid, width);
			if (l < 0)
				l = 0;
			if (*jobfmt == '?') {
				if (l == 0)
					jobfmt++;
				else
					jobfmt += 3;
			}
			break;
		case 'p': /* process ID */
			l = snprintf(jobid, width, "%u", current->pid);
			break;
		case 'u': /* user ID */
			l = snprintf(jobid, width, "%u",
				     from_kuid(&init_user_ns, current_fsuid()));
			break;
		case '\0': /* '%' at end of format string */
			l = 0;
			goto out;
		default: /* drop unknown %x format strings */
			l = 0;
			break;
		}
		if (l >= width)
			l = width-1;

		jobid += l;
		joblen -= l;
	}
	/*
	 * This points at the end of the buffer, so long as jobid is always
	 * incremented the same amount as joblen is decremented.
	 */
out:
	jobid[joblen - 1] = '\0';

	return joblen < 0 ? -EOVERFLOW : 0;
}

/*
 * Hash initialization, copied from server-side job stats bucket sizes
 */
#define HASH_JOBID_BKT_BITS 5
#define HASH_JOBID_CUR_BITS 7
#define HASH_JOBID_MAX_BITS 12

int jobid_cache_init(void)
{
	int rc = 0;
	ENTRY;

	if (jobid_hash)
		return 0;

	spin_lock_init(&jobid_hash_lock);
	jobid_hash = cfs_hash_create("JOBID_HASH", HASH_JOBID_CUR_BITS,
				     HASH_JOBID_MAX_BITS, HASH_JOBID_BKT_BITS,
				     0, CFS_HASH_MIN_THETA, CFS_HASH_MAX_THETA,
				     &jobid_hash_ops, CFS_HASH_DEFAULT);
	if (!jobid_hash) {
		rc = -ENOMEM;
	} else {
		rc = rhashtable_init(&session_jobids, &jobid_params);
		if (rc) {
			cfs_hash_putref(jobid_hash);
			jobid_hash = NULL;
		}
	}

	RETURN(rc);
}
EXPORT_SYMBOL(jobid_cache_init);

void jobid_cache_fini(void)
{
	struct cfs_hash *tmp_hash;
	ENTRY;

	spin_lock(&jobid_hash_lock);
	tmp_hash = jobid_hash;
	jobid_hash = NULL;
	spin_unlock(&jobid_hash_lock);

	cancel_delayed_work_sync(&jobid_prune_work);
	cancel_delayed_work_sync(&jobid_pidmap_gc_work);

	if (tmp_hash != NULL) {
		cfs_hash_cond_del(tmp_hash, jobid_should_free_item, NULL);
		cfs_hash_putref(tmp_hash);

		rhashtable_free_and_destroy(&session_jobids, jobid_free, NULL);
	}


	EXIT;
}
EXPORT_SYMBOL(jobid_cache_fini);

/*
 * Hash operations for pid<->jobid
 */
static unsigned int
jobid_hashfn(struct cfs_hash *hs, const void *key, const unsigned int bits)
{
	return cfs_hash_djb2_hash(key, sizeof(pid_t), bits);
}

static void *jobid_key(struct hlist_node *hnode)
{
	struct jobid_pid_map *pidmap;

	pidmap = hlist_entry(hnode, struct jobid_pid_map, jp_hash);
	return &pidmap->jp_pid;
}

static int jobid_keycmp(const void *key, struct hlist_node *hnode)
{
	const pid_t *pid_key1;
	const pid_t *pid_key2;

	LASSERT(key != NULL);
	pid_key1 = (pid_t *)key;
	pid_key2 = (pid_t *)jobid_key(hnode);

	return *pid_key1 == *pid_key2;
}

static void *jobid_object(struct hlist_node *hnode)
{
	return hlist_entry(hnode, struct jobid_pid_map, jp_hash);
}

static void jobid_get(struct cfs_hash *hs, struct hlist_node *hnode)
{
	struct jobid_pid_map *pidmap;

	pidmap = hlist_entry(hnode, struct jobid_pid_map, jp_hash);

	kref_get(&pidmap->jp_refcount);
}

static void jobid_put_locked_free(struct kref *kref)
{
	struct jobid_pid_map *pidmap = container_of(kref, struct jobid_pid_map,
						    jp_refcount);

	CDEBUG(D_INFO, "Freeing: %d->%s\n", pidmap->jp_pid, pidmap->jp_jobid);
	OBD_FREE_PTR(pidmap);
}

static void jobid_put_locked(struct cfs_hash *hs, struct hlist_node *hnode)
{
	struct jobid_pid_map *pidmap;

	if (hnode == NULL)
		return;

	pidmap = hlist_entry(hnode, struct jobid_pid_map, jp_hash);
	LASSERT(kref_read(&pidmap->jp_refcount) > 0);
	kref_put(&pidmap->jp_refcount, jobid_put_locked_free);
}

static struct cfs_hash_ops jobid_hash_ops = {
	.hs_hash	= jobid_hashfn,
	.hs_keycmp	= jobid_keycmp,
	.hs_key		= jobid_key,
	.hs_object	= jobid_object,
	.hs_get		= jobid_get,
	.hs_put		= jobid_put_locked,
	.hs_put_locked	= jobid_put_locked,
};

/**
 * Generate the job identifier string for this process for tracking purposes.
 *
 * Fill in @jobid string based on the value of obd_jobid_var:
 * JOBSTATS_DISABLE:      none
 * JOBSTATS_NODELOCAL:    content of obd_jobid_name (jobid_interpret_string())
 * JOBSTATS_PROCNAME_UID: process name/UID
 * JOBSTATS_SESSION       per-session value set by
 *                            /sys/fs/lustre/jobid_this_session
 * anything else:         look up obd_jobid_var in the processes environment
 *
 * Return -ve error number, 0 on success.
 */
int lustre_get_jobid(char *jobid, size_t joblen)
{
	int len = min_t(int, joblen, LUSTRE_JOBID_SIZE);
	int rc = 0;
	ENTRY;

	if (unlikely(joblen < 2)) {
		if (joblen == 1)
			jobid[0] = '\0';
		RETURN(-EINVAL);
	}

	if (strcmp(obd_jobid_var, JOBSTATS_DISABLE) == 0) {
		/* Jobstats isn't enabled */
		memset(jobid, 0, joblen);
		RETURN(0);
	}

	if (strcmp(obd_jobid_var, JOBSTATS_NODELOCAL) == 0) {
		/* Whole node dedicated to single job */
		rc = jobid_interpret_string(obd_jobid_name, jobid, len);
	} else if (strcmp(obd_jobid_var, JOBSTATS_PROCNAME_UID) == 0) {
		rc = jobid_interpret_string("%e.%u", jobid, len);
	} else if (strcmp(obd_jobid_var, JOBSTATS_SESSION) == 0 ||
		   jobid_name_is_valid(current->comm)) {
		/*
		 * per-process jobid wanted, either from environment or from
		 * per-session setting.
		 * If obd_jobid_name contains "%j" or if getting the per-process
		 * jobid directly fails, fall back to using obd_jobid_name.
		 */
		rc = -EAGAIN;
		if (!strnstr(obd_jobid_name, "%j", joblen))
			rc = jobid_get_from_cache(jobid, len);

		/* fall back to jobid_name if jobid_var not available */
		if (rc < 0) {
			int rc2 = jobid_interpret_string(obd_jobid_name,
							 jobid, len);
			if (!rc2)
				rc = 0;
		}
	}

	RETURN(rc);
}
EXPORT_SYMBOL(lustre_get_jobid);

/*
 * lustre_jobid_clear
 *
 * Search cache for JobID given by @find_jobid.
 * If any entries in the hash table match the value, they are removed
 */
void lustre_jobid_clear(const char *find_jobid)
{
	char jobid[LUSTRE_JOBID_SIZE];
	char *end;

	if (jobid_hash == NULL)
		return;

	strscpy(jobid, find_jobid, sizeof(jobid));
	/* trim \n off the end of the incoming jobid */
	end = strchr(jobid, '\n');
	if (end && *end == '\n')
		*end = '\0';

	CDEBUG(D_INFO, "Clearing Jobid: %s\n", jobid);
	cfs_hash_cond_del(jobid_hash, jobid_should_free_item, jobid);

	CDEBUG(D_INFO, "%d items remain in jobID table\n",
	       atomic_read(&jobid_hash->hs_count));
}