#include <sys/param.h>
#include <sys/types.h>
#include <sys/pathname.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/systm.h>
#include <sys/unistd.h>
#include <sys/door.h>
#include <sys/socket.h>
#include <nfs/export.h>
#include <nfs/nfs_cmd.h>
#include <sys/kmem.h>
#include <sys/sunddi.h>
#define NFSCMD_DR_TRYCNT 8
#ifdef nextdp
#undef nextdp
#endif
#define nextdp(dp) ((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
typedef struct nfscmd_globals {
kmutex_t nfscmd_lock;
door_handle_t nfscmd_dh;
} nfscmd_globals_t;
static zone_key_t nfscmd_zone_key;
static struct charset_cache *nfscmd_charmap(exportinfo_t *exi,
struct sockaddr *sp);
static void *nfscmd_zone_init(zoneid_t);
static void nfscmd_zone_fini(zoneid_t, void *);
void
nfscmd_args(uint_t did)
{
nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
mutex_enter(&ncg->nfscmd_lock);
if (ncg->nfscmd_dh != NULL)
door_ki_rele(ncg->nfscmd_dh);
ncg->nfscmd_dh = door_ki_lookup(did);
mutex_exit(&ncg->nfscmd_lock);
}
void
nfscmd_init(void)
{
zone_key_create(&nfscmd_zone_key, nfscmd_zone_init,
NULL, nfscmd_zone_fini);
}
void
nfscmd_fini(void)
{
(void) zone_key_delete(nfscmd_zone_key);
}
static void *
nfscmd_zone_init(zoneid_t zoneid)
{
nfscmd_globals_t *ncg;
ncg = kmem_zalloc(sizeof (*ncg), KM_SLEEP);
mutex_init(&ncg->nfscmd_lock, NULL, MUTEX_DEFAULT, NULL);
return (ncg);
}
static void
nfscmd_zone_fini(zoneid_t zoneid, void *data)
{
nfscmd_globals_t *ncg = data;
mutex_destroy(&ncg->nfscmd_lock);
if (ncg->nfscmd_dh)
door_ki_rele(ncg->nfscmd_dh);
kmem_free(ncg, sizeof (*ncg));
}
int
nfscmd_send(nfscmd_arg_t *arg, nfscmd_res_t *res)
{
door_handle_t dh;
door_arg_t da;
door_info_t di;
int ntries = 0;
int last = 0;
nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
retry:
mutex_enter(&ncg->nfscmd_lock);
dh = ncg->nfscmd_dh;
if (dh != NULL)
door_ki_hold(dh);
mutex_exit(&ncg->nfscmd_lock);
if (dh == NULL) {
if (++ntries % NFSCMD_DR_TRYCNT) {
delay(hz);
goto retry;
}
return (NFSCMD_ERR_DROP);
}
da.data_ptr = (char *)arg;
da.data_size = sizeof (nfscmd_arg_t);
da.desc_ptr = NULL;
da.desc_num = 0;
da.rbuf = (char *)res;
da.rsize = sizeof (nfscmd_res_t);
switch (door_ki_upcall(dh, &da)) {
case 0:
break;
case EAGAIN:
door_ki_rele(dh);
delay(hz);
goto retry;
case EINTR:
if (!door_ki_info(dh, &di)) {
if (di.di_attributes & DOOR_REVOKED) {
mutex_enter(&ncg->nfscmd_lock);
if (dh == ncg->nfscmd_dh)
ncg->nfscmd_dh = NULL;
mutex_exit(&ncg->nfscmd_lock);
door_ki_rele(dh);
delay(hz);
goto retry;
}
door_ki_rele(dh);
}
case EBADF:
case EINVAL:
default:
door_ki_rele(dh);
if (!last) {
delay(hz);
last++;
goto retry;
}
res->error = NFSCMD_ERR_FAIL;
break;
}
return (res->error);
}
struct charset_cache *
nfscmd_findmap(struct exportinfo *exi, struct sockaddr *sp)
{
struct charset_cache *charset;
ASSERT(exi != NULL);
ASSERT(sp != NULL);
if (exi == NULL || sp == NULL)
return (NULL);
mutex_enter(&exi->exi_lock);
if (!(exi->exi_export.ex_flags & EX_CHARMAP)) {
mutex_exit(&exi->exi_lock);
return (NULL);
}
for (charset = exi->exi_charset;
charset != NULL;
charset = charset->next) {
if (bcmp(sp, &charset->client_addr,
sizeof (struct sockaddr)) == 0)
break;
}
mutex_exit(&exi->exi_lock);
if (charset == NULL)
charset = nfscmd_charmap(exi, sp);
return (charset);
}
static struct charset_cache *
nfscmd_insert_charmap(struct exportinfo *exi, struct sockaddr *sp, char *name)
{
struct charset_cache *charset;
charset = (struct charset_cache *)
kmem_zalloc(sizeof (struct charset_cache), KM_SLEEP);
if (charset == NULL)
return (NULL);
if (name != NULL) {
charset->inbound = kiconv_open("UTF-8", name);
charset->outbound = kiconv_open(name, "UTF-8");
}
charset->client_addr = *sp;
mutex_enter(&exi->exi_lock);
charset->next = exi->exi_charset;
exi->exi_charset = charset;
mutex_exit(&exi->exi_lock);
return (charset);
}
static struct charset_cache *
nfscmd_charmap(exportinfo_t *exi, struct sockaddr *sp)
{
nfscmd_arg_t req;
int ret;
char *path;
nfscmd_res_t res;
struct charset_cache *charset;
path = exi->exi_export.ex_path;
if (path == NULL)
return (NULL);
req.cmd = NFSCMD_CHARMAP_LOOKUP;
req.version = NFSCMD_VERSION;
req.arg.charmap.addr = *sp;
(void) strncpy(req.arg.charmap.path, path, MAXPATHLEN);
bzero((caddr_t)&res, sizeof (nfscmd_res_t));
ret = nfscmd_send(&req, &res);
if (ret == NFSCMD_ERR_SUCCESS)
charset = nfscmd_insert_charmap(exi, sp,
res.result.charmap.codeset);
else
charset = nfscmd_insert_charmap(exi, sp, NULL);
return (charset);
}
char *
nfscmd_convname(struct sockaddr *ca, struct exportinfo *exi, char *name,
int inbound, size_t size)
{
char *newname;
char *holdname;
int err;
int ret;
size_t nsize;
size_t osize;
struct charset_cache *charset = NULL;
charset = nfscmd_findmap(exi, ca);
if (charset == NULL ||
(charset->inbound == NULL && inbound) ||
(charset->outbound == NULL && !inbound))
return (name);
newname = kmem_zalloc(size, KM_SLEEP);
nsize = strlen(name);
osize = size;
holdname = newname;
if (inbound)
ret = kiconv(charset->inbound, &name, &nsize,
&holdname, &osize, &err);
else
ret = kiconv(charset->outbound, &name, &nsize,
&holdname, &osize, &err);
if (ret == (size_t)-1) {
kmem_free(newname, size);
newname = NULL;
}
return (newname);
}
char *
nfscmd_convdirent(struct sockaddr *ca, struct exportinfo *exi, char *data,
size_t size, enum nfsstat3 *error)
{
char *newdata;
size_t ret;
size_t nsize;
size_t count;
int err = 0;
char *iname;
char *oname;
struct charset_cache *charset;
charset = nfscmd_findmap(exi, ca);
if (charset == NULL || charset->outbound == (void *)~0)
return (data);
newdata = kmem_zalloc(size, KM_SLEEP);
nsize = strlen(((struct dirent64 *)data)->d_name);
count = size;
bcopy(data, newdata, sizeof (struct dirent64));
iname = ((struct dirent64 *)data)->d_name;
oname = ((struct dirent64 *)newdata)->d_name;
ret = kiconv(charset->outbound, &iname, &nsize, &oname, &count, &err);
if (ret == (size_t)-1) {
kmem_free(newdata, size);
newdata = NULL;
if (err == E2BIG) {
if (error != NULL)
*error = NFS3ERR_NAMETOOLONG;
} else {
newdata = data;
}
} else {
ret = strlen(((struct dirent64 *)newdata)->d_name);
((struct dirent64 *)newdata)->d_reclen =
DIRENT64_RECLEN(ret + 1);
}
return (newdata);
}
size_t
nfscmd_convdirplus(struct sockaddr *ca, struct exportinfo *exi, char *data,
size_t nents, size_t maxsize, char **ndata)
{
char *newdata;
size_t nsize;
struct dirent64 *dp;
struct dirent64 *ndp;
size_t i;
size_t ret;
char *iname;
char *oname;
size_t ilen;
size_t olen;
int err;
size_t skipped;
struct charset_cache *charset;
*ndata = data;
charset = nfscmd_findmap(exi, ca);
if (charset == NULL || charset->outbound == (void *)~0)
return (0);
newdata = kmem_zalloc(maxsize, KM_SLEEP);
nsize = 0;
dp = (struct dirent64 *)data;
ndp = (struct dirent64 *)newdata;
for (skipped = 0, i = 0; i < nents; i++) {
if ((maxsize - nsize) < dp->d_reclen)
break;
*ndp = *dp;
iname = dp->d_name;
ilen = strlen(iname);
oname = ndp->d_name;
olen = MIN(MAXNAMELEN, maxsize - nsize);
ret = kiconv(charset->outbound, &iname, &ilen, &oname,
&olen, &err);
if (ret == (size_t)-1) {
switch (err) {
default:
case E2BIG:
break;
case EILSEQ:
skipped++;
dp = nextdp(dp);
continue;
}
}
ilen = MIN(MAXNAMELEN, maxsize - nsize) - olen;
ndp->d_name[ilen] = '\0';
ndp->d_reclen = DIRENT64_RECLEN(strlen(ndp->d_name) + 1);
nsize += ndp->d_reclen;
dp = nextdp(dp);
ndp = nextdp(ndp);
}
*ndata = newdata;
return (nents - (i + skipped));
}
size_t
nfscmd_countents(char *data, size_t len)
{
struct dirent64 *dp = (struct dirent64 *)data;
size_t curlen;
size_t reclen;
size_t nents;
for (nents = 0, curlen = 0; curlen < len; curlen += reclen, nents++) {
reclen = dp->d_reclen;
dp = nextdp(dp);
}
return (nents);
}
size_t
nfscmd_dropped_entrysize(struct dirent64 *dir, size_t drop, size_t nents)
{
size_t size;
size_t i;
for (i = nents - drop; i > 0 && dir != NULL; i--)
dir = nextdp(dir);
if (dir == NULL)
return (0);
for (size = 0, i = 0; i < drop && dir != NULL; i++) {
size += dir->d_reclen;
dir = nextdp(dir);
}
return (size);
}