/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * nis_misc.c
 */

/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * nis_misc.c
 *
 * This module contains miscellaneous library functions.
 */

#include "mt.h"
#include <string.h>
#include <syslog.h>
#include <malloc.h>
#include <rpc/rpc.h>
#include <rpcsvc/nis.h>
#include <tiuser.h>
#include <netdir.h>
#include <netinet/in.h>
#include <strings.h>
#include "nis_clnt.h"
#include "nis_local.h"

static void nis_sort_server_endpoints_inet(nis_server *);
extern char *handle_to_server_name(CLIENT *);
extern void *__inet_get_local_interfaces();
extern FILE *__nis_debug_file;


/* ARGSUSED */
int
__clear_directory_local(nis_name n)
{
	return (1);
}

int (*__clear_directory_ptr)(nis_name) = __clear_directory_local;

/*
 * __nis_pingproc()
 *
 * This function will send a  ping "message" to a remote server.
 * It doesn't bother to see if there are any results since the ping
 * is defined to be unreliable.
 */
void
__nis_pingproc(
	nis_server	*srv,	/* Server to talk to 		*/
	nis_name	name,	/* Directory that changed 	*/
	uint32_t	mtime)	/* When it changed		*/
{
	CLIENT		*clnt;
	ping_args	args;
	struct timeval	tv;

	clnt = nis_make_rpchandle(srv, 0, NIS_PROG, NIS_VERSION,
				ZMH_DG|ZMH_AUTH, 0, 0);
	if (! clnt)
		return;

	tv.tv_sec = 0;
	tv.tv_usec = 0;
	args.dir = name;
	args.stamp = mtime;
	(void) clnt_call(clnt, NIS_PING, xdr_ping_args, (char *)&args,
			xdr_void, 0, tv);
	clnt_destroy(clnt);
}

/*
 * nis_ping()
 *
 * This function is used to ping all of the servers that serve a given
 * directory. The point of the ping is to inform them that something
 * has changed in the directory and they should go off and find what it
 * is. Note that we avoid pinging ourselves for optimisation. During a
 * replica/master switch the location of the master object in the array
 * will briefly change from server[0] to the offset corresponding to
 * the new master (the old replica). When everything has settled down
 * after the switch, the master will once again be in server[0] of the
 * directory object. The object parameter is optional for clients
 * (REQUIRED FOR SERVERS) this is the object describing the directory.
 */

void
nis_ping(nis_name name, uint32_t mtime, nis_object *obj)
{
	nis_server	**srvs;
	nis_server	*s, *list;
	int		i, ns;
	nis_name	thishost = nis_local_host();

	if (obj) {
		if (name == 0)
			name = obj->DI_data.do_name;
		list = obj->DI_data.do_servers.do_servers_val;
		ns = obj->DI_data.do_servers.do_servers_len;

		for (i = 0, s = &(list[0]); i < ns; i++, s = &(list[i])) {

			if (nis_dir_cmp(s->name, thishost) == SAME_NAME) {
				continue;
			}

			__nis_pingproc(s, name, mtime);
		}
	} else {
		srvs = nis_getservlist(name);
		if (! srvs)
			return;

		for (i = 0, s = srvs[0]; s; i++, s = srvs[i]) {

			if (nis_dir_cmp(s->name, thishost) == SAME_NAME) {
				continue;
			}
			__nis_pingproc(s, name, mtime);

		}
		nis_freeservlist(srvs);
	}
}


/*
 * nis_dumplog(host, name, time)
 *
 * This function will dump log entries from the indicated host to the
 * caller. It is used by the replica servers to get the updates that have
 * occurred on a directory since the indicated time.
 */

log_result *
nis_dumplog(
	nis_server	*host,	/* Server to talk to		*/
	nis_name	name,	/* Directory name to dump.	*/
	uint32_t	dtime)	/* Last _valid_ timestamp.	*/
{
	CLIENT			*clnt;
	dump_args		da;
	struct timeval		tv;
	enum clnt_stat		stat;
	log_result	*result_ptr;

	result_ptr = calloc(1, sizeof (log_result));
	if (result_ptr == NULL) {
		syslog(LOG_ERR, "nis_dumplog: Client out of memory.");
		return (NULL);
	}

	clnt = nis_make_rpchandle(host, 0, NIS_PROG, NIS_VERSION,
				ZMH_VC+ZMH_AUTH, 0, 0);
	if (! clnt) {
		result_ptr->lr_status = NIS_NAMEUNREACHABLE;
		return (result_ptr);
	}
	(void) memset((char *)&da, 0, sizeof (da));
	da.da_dir = name;
	da.da_time = dtime;
	tv.tv_sec = NIS_DUMP_TIMEOUT;
	tv.tv_usec = 0;
	stat = clnt_call(clnt, NIS_DUMPLOG,
			xdr_dump_args, (char *)&da,
			xdr_log_result, (char *)result_ptr, tv);
	auth_destroy(clnt->cl_auth);
	clnt_destroy(clnt);

	/*
	 * Now see if the RPC succeeded. Note that we have
	 * to check for local vs. remote errors in order to
	 * know whether the contents of the log_result record
	 * (result_ptr) are meaningful.
	 */
	switch (stat) {
	case RPC_CANTENCODEARGS:
	case RPC_CANTDECODERES:
	case RPC_CANTSEND:
	case RPC_CANTRECV:
	case RPC_TIMEDOUT:
	case RPC_INTR:
		syslog(LOG_WARNING, "nis_dumplog: RPC error %d", stat);
		/*
		 * This is a local error, so just return a
		 * generic RPC error.
		 */
		result_ptr->lr_status = NIS_RPCERROR;
		break;

	default:
		/*
		 * All other return values mean that result_ptr
		 * already has a valid status code.
		 */
		break;
	}

	return (result_ptr);
}

/*
 * nis_dump(host, name, cback)
 *
 * This function will dump an entire directory from the indicated host.
 * It uses a callback function to minimize the memory requirements on
 * the client and server.
 */

log_result *
nis_dump(
	nis_server	*host,	/* Server to talk to		*/
	nis_name	name,	/* Directory name to dump.	*/
	int		(*cback)()) /* Callback function	*/
{
	CLIENT			*clnt;
	dump_args		da;
	struct timeval		tv;
	enum clnt_stat		stat;
	int			err;
	log_result		*result_ptr;

	result_ptr = calloc(1, sizeof (log_result));
	if (result_ptr == NULL) {
		syslog(LOG_ERR, "nis_dump: Client out of memory.");
		return (NULL);
	}

	clnt = nis_make_rpchandle(host, 0, NIS_PROG, NIS_VERSION,
				ZMH_VC+ZMH_AUTH, 0, 0);
	if (!clnt) {
		result_ptr->lr_status = NIS_NAMEUNREACHABLE;
		return (result_ptr);
	}
	(void) mutex_lock(&__nis_callback_lock);
	(void) memset((char *)&da, 0, sizeof (da));
	da.da_dir = name;
	da.da_time = 0;
	da.da_cbhost.da_cbhost_len = 1;
	da.da_cbhost.da_cbhost_val = __nis_init_dump_callback(clnt, cback,
								NULL);
	if (! da.da_cbhost.da_cbhost_val) {
		(void) mutex_unlock(&__nis_callback_lock);
		result_ptr->lr_status = NIS_CBERROR;
		auth_destroy(clnt->cl_auth);
		clnt_destroy(clnt);
		return (result_ptr);
	}

	/*
	 * The value of the NIS_DUMP_TIMEOUT is applicable only for the
	 * dump to get initiated.
	 */
	tv.tv_sec = NIS_DUMP_TIMEOUT;
	tv.tv_usec = 0;
	stat = clnt_call(clnt, NIS_DUMP, xdr_dump_args, (char *)&da,
			xdr_log_result, (char *)result_ptr, tv);
	if (stat != RPC_SUCCESS) {
		result_ptr->lr_status = NIS_RPCERROR;
	} else if (result_ptr->lr_status == NIS_CBRESULTS) {
		(*__clear_directory_ptr)(name);
		err = __nis_run_dump_callback(&(result_ptr->lr_cookie),
					NIS_CALLBACK, 0, clnt);
		if (err < 0)
			result_ptr->lr_status = NIS_CBERROR;
	}
	(void) mutex_unlock(&__nis_callback_lock);
	auth_destroy(clnt->cl_auth);
	clnt_destroy(clnt);
	return (result_ptr);
}

/*
 *  Sort server endpoints so that local addresses appear
 *  before remote addresses.
 */
void
nis_sort_directory_servers(directory_obj *slist)
{
	int i;

	int nsvrs = slist->do_servers.do_servers_len;
	nis_server *svrs = slist->do_servers.do_servers_val;

	for (i = 0; i < nsvrs; i++) {
		nis_sort_server_endpoints_inet(&svrs[i]);
	}
}

static
int
is_local(void *local_interfaces, struct netconfig *ncp, char *uaddr)
{
	return (__inet_uaddr_is_local(local_interfaces, ncp, uaddr));
}

static
int
is_remote(void *local_interfaces, struct netconfig *ncp, char *uaddr)
{
	return (!is_local(local_interfaces, ncp, uaddr));
}

void
__nis_swap_endpoints(endpoint *e1, endpoint *e2)
{
	char *t;

	t = e1->uaddr;
	e1->uaddr = e2->uaddr;
	e2->uaddr = t;

	t = e1->family;
	e1->family = e2->family;
	e2->family = t;

	t = e1->proto;
	e1->proto = e2->proto;
	e2->proto = t;
}

/*
 *  Sort a list of server endpoints so that address for local interfaces
 *  occur before remote interfaces.  If an error occurs (e.g., no memory),
 *  we just clean up and return; we end up not sorting the endpoints, but
 *  this is just for optimization anyway.
 *
 *  There is a lot of work in this routine, so it should not be called
 *  frequently.
 */
static
void
nis_sort_server_endpoints_inet(nis_server *svr)
{
	int i;
	int j;
	int neps = svr->ep.ep_len;
	endpoint *eps = svr->ep.ep_val;
	struct netconfig *ncp, *ncp_inet = 0, *ncp_inet6 = 0;
	void *local_interfaces;
	void *nch;

	nch = setnetconfig();
	if (nch == 0)
		return;

	/* find any inet entry so we can do uaddr2taddr */
	while ((ncp = getnetconfig(nch)) != 0 &&
		ncp_inet == 0 && ncp_inet6 == 0) {
		if (strcmp(ncp->nc_protofmly, NC_INET) == 0)
			ncp_inet = ncp;
		else if (strcmp(ncp->nc_protofmly, NC_INET6))
			ncp_inet6 = ncp;
	}
	if (ncp_inet == 0 && ncp_inet6 == 0) {
		(void) endnetconfig(nch);
		return;
	}

	local_interfaces = __inet_get_local_interfaces();
	if (local_interfaces == 0) {
		(void) endnetconfig(nch);
		return;
	}

	/*
	 *  Sort endpoints so local inet addresses are first.  The
	 *  variable 'i' points to the beginning of the array,
	 *  and 'j' points to the end.  We advance 'i' as long
	 *  as it indexes a non-inet endpoint or a local endpoint.
	 *  We retract 'j' as long as it indexes a non-inet endpoint
	 *  or a remote endpoint.  If either of these cases fail,
	 *  then 'i' is pointing at a remote endpoint and 'j' is
	 *  pointing at a local endpoint.  We swap them, adjust
	 *  the indexes, and continue.  When the indexes cross
	 *  we are done.
	 */
	i = 0;
	j = neps - 1;
	while (i < j) {
		if ((strcmp(eps[i].family, NC_INET) != 0 &&
			strcmp(eps[i].family, NC_INET6) != 0) ||
		    is_local(local_interfaces, ncp, eps[i].uaddr)) {
			i++;
			continue;
		}

		if ((strcmp(eps[j].family, NC_INET) != 0 &&
			strcmp(eps[j].family, NC_INET6) != 0) ||
		    is_remote(local_interfaces, ncp, eps[j].uaddr)) {
			--j;
			continue;
		}

		__nis_swap_endpoints(&eps[i], &eps[j]);
		i++;
		--j;
	}

	/* clean up */
	__inet_free_local_interfaces(local_interfaces);
	(void) endnetconfig(nch);
}

/*
 * In the pre-IPv6 code, secure RPC has a bug such that it doesn't look
 * at the endpoint 'family' field when selecting an endpoint to use for
 * time synchronization. In order to protect that broken code from itself,
 * we set the endpoint 'proto' to 'nc_netid' (i.e., "udp6" or "tcp6")
 * rather than 'nc_proto' ("udp"/"tcp") if 'nc_family' is "inet6".
 *
 * The __nis_netconfig2ep() and __nis_netconfig_matches_ep() service
 * functions below simplify endpoint manipulation by implementing the
 * rules above.
 */

void
__nis_netconfig2ep(struct netconfig *nc, endpoint *ep) {

	if (nc == 0 || ep == 0)
		return;

	ep->family = strdup(nc->nc_protofmly);

	if (strcmp(ep->family, "inet6") == 0) {
		ep->proto = strdup(nc->nc_netid);
	} else {
		ep->proto = strdup(nc->nc_proto);
	}
}

bool_t
__nis_netconfig_matches_ep(struct netconfig *nc, endpoint *ep) {

	if (nc == 0 || ep == 0)
		return (FALSE);

	if (strcmp(nc->nc_protofmly, ep->family) != 0)
		return (FALSE);

	if (strcmp(ep->family, "inet6") == 0)
		return (strcmp(nc->nc_netid, ep->proto) == 0 ||
			strcmp(nc->nc_proto, ep->proto) == 0);
	else
		return (strcmp(nc->nc_proto, ep->proto) == 0);

}

struct netconfig_list {
	struct netconfig *nc;
	struct netconfig_list *next;
};

static struct netconfig_list *ncl;

struct netconfig *
__nis_get_netconfig(endpoint *ep)
{
	void *nch;
	struct netconfig *nc;
	struct netconfig_list *p;

	for (p = ncl; p; p = p->next) {
		if (__nis_netconfig_matches_ep(p->nc, ep)) {
			return (p->nc);
		}
	}

	nch = setnetconfig();
	if (nch == 0)
		return (0);

	while ((nc = getnetconfig(nch)) != 0) {
		if (__nis_netconfig_matches_ep(nc, ep))
			break;
	}
	/*
	 *  We call getnetconfigent to allocate a copy of the
	 *  netconfig entry.
	 */
	if (nc) {
		p = malloc(sizeof (*p));
		if (p == 0)
			return (0);
		p->nc = getnetconfigent(nc->nc_netid);
		p->next = ncl;
		ncl = p;
	}
	(void) endnetconfig(nch);

	return (nc);
}

void
nis_free_binding(nis_bound_directory *binding)
{
	xdr_free((xdrproc_t)xdr_nis_bound_directory, (char *)binding);
	free(binding);
}

void
__free_fdresult(fd_result *res)
{
	xdr_free((xdrproc_t)xdr_fd_result, (char *)res);
	free(res);
}

endpoint *
__endpoint_dup(endpoint *src, endpoint *dst)
{
	if (dst == NULL) {
		dst = malloc(sizeof (endpoint));
		if (dst == NULL)
			return (NULL);
	}

	dst->family = src->family?strdup(src->family):0;
	dst->proto = src->proto?strdup(src->proto):0;
	dst->uaddr = src->uaddr?strdup(src->uaddr):0;

	return (dst);
}

void
__endpoint_free(endpoint *ep)
{
	if (ep) {
		free(ep->family);
		free(ep->proto);
		free(ep->uaddr);
		free(ep);
	}
}

endpoint *
__get_bound_endpoint(nis_bound_directory *binding, int n)
{
	endpoint *ep;
	nis_server *srv;
	nis_bound_endpoint *bep;

	bep = &binding->bep_val[n];
	srv = binding->dobj.do_servers.do_servers_val;
	ep = &srv[bep->hostnum].ep.ep_val[bep->epnum];
	return (ep);
}

nis_server *
__nis_server_dup(nis_server *src, nis_server *dst)
{
	if (dst == NULL) {
		dst = malloc(sizeof (nis_server));
		if (dst == NULL)
			return (NULL);
	}
	(void) memset((char *)dst, 0, sizeof (nis_server));
	/* LINTED pointer cast */
	return ((nis_server *)
		__nis_xdr_dup(xdr_nis_server, (char *)src, (char *)dst));
}


char *
__nis_xdr_dup(xdrproc_t proc, char *src, char *dst)
{
	uint_t size;
	char *data;
	XDR xdrs;

	size = xdr_sizeof(proc, src);
	data = malloc(size);
	if (data == NULL)
		return (NULL);

	xdrmem_create(&xdrs, data, size, XDR_ENCODE);
	if (!proc(&xdrs, src)) {
		free(data);
		return (NULL);
	}

	xdrmem_create(&xdrs, data, size, XDR_DECODE);
	if (!proc(&xdrs, dst)) {
		free(data);
		return (NULL);
	}

	free(data);
	return (dst);
}

void
__nis_path_free(char **names, int len)
{
	int i;

	for (i = 0; i < len; i++)
		free(names[i]);
	free(names);
}

/*
 * __nis_path
 *
 * Given two path strings, *from and *to, work out the list of names
 * between them. The length of that path and the pointer to the list
 * of pointers to the name strings are returned to the caller using
 * path_length and namesp. As the names list is a pointer to a list of
 * pointers, the caller must pass the address of the pointer to the
 * pointer to the list of pointers, hence the unusual "char ***"
 * type. It is the callers responsibility to free **namesp.
 */
nis_error
__nis_path(char *from, char *to, int *path_length, char ***namesp)
{
	int i;
	int n;
	int start;
	int end;
	int st, dircmp, lastdircmp;
	char *tfrom = from;
	char *tto = to;
	char **names;

	dircmp = st = nis_dir_cmp(from, to);
	if (st == BAD_NAME)
		return (NIS_BADNAME);

	/* figure out how long path is */
	n = 0;
	if (st == HIGHER_NAME) {
		while ((dircmp = nis_dir_cmp(from, to)) == HIGHER_NAME) {
			n++;
			to = nis_domain_of(to);
		}
		if (dircmp != SAME_NAME) {
			/* Unrecoverable error */
			dircmp = BAD_NAME;
		}
	} else if (st == LOWER_NAME) {
		from = nis_domain_of(from);
		while ((dircmp = nis_dir_cmp(from, to)) == LOWER_NAME) {
			n++;
			from = nis_domain_of(from);
		}
		if (dircmp != SAME_NAME) {
			/* Unrecoverable error */
			dircmp = BAD_NAME;
		}
		n++;	/* include actual target */
	} else if (st == NOT_SEQUENTIAL) {
		/* names are not sequential */
		from = nis_domain_of(from);
		while ((dircmp = nis_dir_cmp(from, to)) == NOT_SEQUENTIAL) {
			n++;
			from = nis_domain_of(from);
		}
		n++;	/* include common parent */
		lastdircmp = dircmp; /* Handle HIGHER or LOWER */
		while ((dircmp = nis_dir_cmp(from, to)) == lastdircmp) {
			n++;
			lastdircmp = dircmp;
			to = nis_domain_of(to);
		}
		if (dircmp != SAME_NAME) {
			/* Unrecoverable error */
			dircmp = BAD_NAME;
		}
	}

	if (dircmp == BAD_NAME) {
		syslog(LOG_WARNING, "__nis_path: Unable to walk "
		    "from %s to %s", tfrom, tto);
		return (NIS_BADNAME);
	}

	names = malloc(n * sizeof (char *));
	if (names == NULL)
		return (NIS_NOMEMORY);

	start = 0;
	end = n;
	from = tfrom;
	to = tto;

	/*
	 * Go through again, this time storing names.
	 * We shouldn't need to check the loops will terminate
	 * on the SAME_NAME condition as we've already checked for
	 * errors in the previous loop.
	 */
	if (st == HIGHER_NAME) {
		while (nis_dir_cmp(from, to) != SAME_NAME) {
			names[--end] = strdup(to);
			to = nis_domain_of(to);
		}
	} else if (st == LOWER_NAME) {
		from = nis_domain_of(from);
		while (nis_dir_cmp(from, to) != SAME_NAME) {
			names[start++] = strdup(from);
			from = nis_domain_of(from);
		}
		names[start++] = strdup(to);	/* include actual target */
	} else if (st == NOT_SEQUENTIAL) {
		/* names are not sequential */
		from = nis_domain_of(from);
		while (nis_dir_cmp(from, to) == NOT_SEQUENTIAL) {
			names[start++] = strdup(from);
			from = nis_domain_of(from);
		}
		names[start++] = strdup(from);	/* include common parent */
		while (nis_dir_cmp(from, to) != SAME_NAME) {
			names[--end] = strdup(to);
			to = nis_domain_of(to);
		}
	}

	/* make sure all of the allocations were successful */
	for (i = 0; i < n; i++) {
		if (names[i] == NULL) {
			__nis_path_free(names, n);
			names = NULL;
			break;
		}
	}

	/* Set the return values */

	*path_length = n;
	*namesp = names;

	return (NIS_SUCCESS);
}

/*
 *  This is a stub function for clients.  There is a version of
 *  it in rpc.nisd that checks to see if the host is listed in
 *  the server list.
 */
int
__nis_host_is_server(nis_server *srv, int nsrv)
{
#ifdef lint
	srv = srv;
	nsrv = nsrv;
#endif /* lint */
	return (0);
}

char *call_names[] = {
	"NULL",
	"NIS_LOOKUP",
	"NIS_ADD",
	"NIS_MODIFY",
	"NIS_REMOVE",
	"NIS_IBLIST",
	"NIS_IBADD",
	"NIS_IBMODIFY",
	"NIS_IBREMOVE",
	"NIS_IBFIRST",
	"NIS_IBNEXT",
	"13",
	"NIS_FINDDIRECTORY",
	"NIS_STATUS",
	"NIS_DUMPLOG",
	"NIS_DUMP",
	"NIS_CALLBACK",
	"NIS_CPTIME",
	"NIS_CHECKPOINT",
	"NIS_PING",
	"NIS_SERVSTATE",
	"NIS_MKDIR",
	"NIS_RMDIR",
	"NIS_UPDKEYS",
};

void
__nis_print_call(CLIENT *clnt, int proc)
{
	char *name;
	char *pname;
	char lbuf[10];

	name = handle_to_server_name(clnt);
	if (proc > NIS_UPDKEYS)
		(void) sprintf(lbuf, "%d", proc);
	else
		pname = call_names[proc];
	(void) fprintf(__nis_debug_file, "calling server %s for %s\n",
	    name, pname);
}

void
__nis_print_rpc_result(enum clnt_stat status)
{
	(void) fprintf(__nis_debug_file, "result:  %s\n", clnt_sperrno(status));
}

void
__nis_print_req(ib_request *req)
{
	int i;
	int nattr = req->ibr_srch.ibr_srch_len;
	nis_attr *attr = req->ibr_srch.ibr_srch_val;

	(void) fprintf(__nis_debug_file, "[");
	for (i = 0; i < nattr; i++) {
		if (i != 0)
			(void) fprintf(__nis_debug_file, ",");
		(void) fprintf(__nis_debug_file, "%s=%s",
			attr[i].zattr_ndx,
			attr[i].zattr_val.zattr_val_val);
	}
	(void) fprintf(__nis_debug_file, "],%s\n", req->ibr_name);
}

void
__nis_print_nsreq(ns_request *req)
{
	(void) fprintf(__nis_debug_file, "%s\n", req->ns_name);
}

void
__nis_print_result(nis_result *res)
{
	(void) fprintf(__nis_debug_file,
		"status=%s, %d object%s, [z=%d, d=%d, a=%d, c=%d]\n",
		nis_sperrno(res->status),
		res->objects.objects_len,
		res->objects.objects_len == 1 ? "" : "s",
		res->zticks,
		res->dticks,
		res->aticks,
		res->cticks);
}

void
__nis_print_fdreq(fd_args *arg)
{
	(void) fprintf(__nis_debug_file, "%s (from %s)\n",
		arg->dir_name, arg->requester);
}