Home | History | Annotate | Download | only in nfs
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #include <sys/param.h>
     27 #include <sys/types.h>
     28 #include <sys/pathname.h>
     29 #include <sys/errno.h>
     30 #include <sys/cmn_err.h>
     31 #include <sys/debug.h>
     32 #include <sys/systm.h>
     33 #include <sys/unistd.h>
     34 #include <sys/door.h>
     35 #include <sys/socket.h>
     36 #include <nfs/export.h>
     37 #include <nfs/nfs_cmd.h>
     38 #include <sys/kmem.h>
     39 #include <sys/sunddi.h>
     40 
     41 #define	NFSCMD_DR_TRYCNT	8
     42 
     43 #ifdef nextdp
     44 #undef nextdp
     45 #endif
     46 #define	nextdp(dp)	((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
     47 
     48 kmutex_t	nfscmd_lock;
     49 door_handle_t   nfscmd_dh;
     50 
     51 void
     52 nfscmd_args(uint_t did)
     53 {
     54 	mutex_enter(&nfscmd_lock);
     55 	if (nfscmd_dh)
     56 		door_ki_rele(nfscmd_dh);
     57 	nfscmd_dh = door_ki_lookup(did);
     58 	mutex_exit(&nfscmd_lock);
     59 }
     60 
     61 void
     62 nfscmd_init(void)
     63 {
     64 	mutex_init(&nfscmd_lock, NULL, MUTEX_DEFAULT, NULL);
     65 }
     66 
     67 void
     68 nfscmd_fini(void)
     69 {
     70 }
     71 
     72 /*
     73  * nfscmd_send(arg, result)
     74  *
     75  * Send a command to the daemon listening on the door.
     76  * The result is returned in the result pointer if the function
     77  * value is zero. If non-zero, it is the error value.
     78  */
     79 
     80 int
     81 nfscmd_send(nfscmd_arg_t *arg, nfscmd_res_t *res)
     82 {
     83 	door_handle_t	dh;
     84 	door_arg_t	da;
     85 	door_info_t	di;
     86 	int		ntries = 0;
     87 	int		last = 0;
     88 
     89 retry:
     90 	mutex_enter(&nfscmd_lock);
     91 	dh = nfscmd_dh;
     92 	if (dh != NULL)
     93 		door_ki_hold(dh);
     94 	mutex_exit(&nfscmd_lock);
     95 
     96 	if (dh == NULL) {
     97 		/*
     98 		 * The rendezvous point has not been established yet !
     99 		 * This could mean that either mountd(1m) has not yet
    100 		 * been started or that _this_ routine nuked the door
    101 		 * handle after receiving an EINTR for a REVOKED door.
    102 		 *
    103 		 * Returning NFSAUTH_DROP will cause the NFS client
    104 		 * to retransmit the request, so let's try to be more
    105 		 * rescillient and attempt for ntries before we bail.
    106 		 */
    107 		if (++ntries % NFSCMD_DR_TRYCNT) {
    108 			delay(hz);
    109 			goto retry;
    110 		}
    111 		return (NFSCMD_ERR_DROP);
    112 	}
    113 
    114 	da.data_ptr = (char *)arg;
    115 	da.data_size = sizeof (nfscmd_arg_t);
    116 	da.desc_ptr = NULL;
    117 	da.desc_num = 0;
    118 	da.rbuf = (char *)res;
    119 	da.rsize = sizeof (nfscmd_res_t);
    120 
    121 	switch (door_ki_upcall(dh, &da)) {
    122 	case 0:
    123 		/* Success */
    124 		break;
    125 	case EAGAIN:
    126 		/* Need to retry a couple of times */
    127 		door_ki_rele(dh);
    128 		delay(hz);
    129 		goto retry;
    130 		/* NOTREACHED */
    131 	case EINTR:
    132 		if (!door_ki_info(dh, &di)) {
    133 			if (di.di_attributes & DOOR_REVOKED) {
    134 				/*
    135 				 * The server barfed and revoked
    136 				 * the (existing) door on us; we
    137 				 * want to wait to give smf(5) a
    138 				 * chance to restart mountd(1m)
    139 				 * and establish a new door handle.
    140 				 */
    141 				mutex_enter(&nfscmd_lock);
    142 				if (dh == nfscmd_dh)
    143 					nfscmd_dh = NULL;
    144 				mutex_exit(&nfscmd_lock);
    145 				door_ki_rele(dh);
    146 				delay(hz);
    147 				goto retry;
    148 			}
    149 			/*
    150 			 * If the door was _not_ revoked on us,
    151 			 * then more than likely we took an INTR,
    152 			 * so we need to fail the operation.
    153 			 */
    154 			door_ki_rele(dh);
    155 		}
    156 		/*
    157 		 * The only failure that can occur from getting
    158 		 * the door info is EINVAL, so we let the code
    159 		 * below handle it.
    160 		 */
    161 		/* FALLTHROUGH */
    162 
    163 	case EBADF:
    164 	case EINVAL:
    165 	default:
    166 		/*
    167 		 * If we have a stale door handle, give smf a last
    168 		 * chance to start it by sleeping for a little bit.
    169 		 * If we're still hosed, we'll fail the call.
    170 		 *
    171 		 * Since we're going to reacquire the door handle
    172 		 * upon the retry, we opt to sleep for a bit and
    173 		 * _not_ to clear mountd_dh. If mountd restarted
    174 		 * and was able to set mountd_dh, we should see
    175 		 * the new instance; if not, we won't get caught
    176 		 * up in the retry/DELAY loop.
    177 		 */
    178 		door_ki_rele(dh);
    179 		if (!last) {
    180 			delay(hz);
    181 			last++;
    182 			goto retry;
    183 		}
    184 		res->error = NFSCMD_ERR_FAIL;
    185 		break;
    186 	}
    187 	return (0);
    188 }
    189 
    190 /*
    191  * nfscmd_findmap(export, addr)
    192  *
    193  * Find a characterset map, if there is one, for the specified client
    194  * address.
    195  */
    196 struct charset_cache *
    197 nfscmd_findmap(struct exportinfo *exi, struct sockaddr *sp)
    198 {
    199 	struct charset_cache *charset;
    200 
    201 	mutex_enter(&exi->exi_lock);
    202 	for (charset = exi->exi_charset;
    203 	    charset != NULL;
    204 	    charset = charset->next) {
    205 		if (bcmp(sp, &charset->client_addr,
    206 		    sizeof (struct sockaddr)) == 0)
    207 			break;
    208 	}
    209 	mutex_exit(&exi->exi_lock);
    210 	return (charset);
    211 }
    212 
    213 /*
    214  * nfscmd_insert_charmap(export, addr, name)
    215  *
    216  * Insert a new character set conversion map into the export structure
    217  * for the share. The entry has the IP address of the client and the
    218  * character set name.
    219  */
    220 
    221 static struct charset_cache *
    222 nfscmd_insert_charmap(struct exportinfo *exi, struct sockaddr *sp, char *name)
    223 {
    224 	struct charset_cache *charset;
    225 
    226 	charset = (struct charset_cache *)
    227 	    kmem_zalloc(sizeof (struct charset_cache), KM_SLEEP);
    228 
    229 	if (charset == NULL)
    230 		return (NULL);
    231 	if (name != NULL) {
    232 		charset->inbound = kiconv_open("UTF-8", name);
    233 		charset->outbound = kiconv_open(name, "UTF-8");
    234 	}
    235 	charset->client_addr = *sp;
    236 	mutex_enter(&exi->exi_lock);
    237 	charset->next = exi->exi_charset;
    238 	exi->exi_charset = charset;
    239 	mutex_exit(&exi->exi_lock);
    240 
    241 	return (charset);
    242 }
    243 
    244 /*
    245  * nfscmd_charmap(response, sp, exi)
    246  *
    247  * Check to see if this client needs a character set conversion. Note
    248  * that the majority of clients will never have this set so it is
    249  * important to not do excessive lookups when there isn't a mapping.
    250  */
    251 
    252 int
    253 nfscmd_charmap(struct exportinfo *exi, struct sockaddr *sp)
    254 {
    255 	nfscmd_arg_t req;
    256 	int ret = NFSCMD_ERR_BADCMD;
    257 	char *path = NULL;
    258 	nfscmd_res_t res;
    259 	struct charset_cache *charset = NULL;
    260 
    261 	if (exi != NULL)
    262 		path = exi->exi_export.ex_path;
    263 
    264 	if (path != NULL && sp != NULL) {
    265 		/*
    266 		 * First check to see if charset has been cached for
    267 		 * this client.
    268 		 */
    269 		charset = nfscmd_findmap(exi, sp);
    270 		if (charset == NULL) {
    271 			/*
    272 			 * Didn't find one so make the request to the
    273 			 * daemon. We need to add the entry in either
    274 			 * case since we want negative as well as
    275 			 * positive cacheing.
    276 			 */
    277 			req.cmd = NFSCMD_CHARMAP_LOOKUP;
    278 			req.version = NFSCMD_VERSION;
    279 			req.arg.charmap.addr = *sp;
    280 			(void) strncpy(req.arg.charmap.path, path, MAXPATHLEN);
    281 			bzero((caddr_t)&res, sizeof (nfscmd_res_t));
    282 			ret = nfscmd_send(&req, &res);
    283 			if (ret == NFSCMD_ERR_SUCCESS)
    284 				charset = nfscmd_insert_charmap(exi, sp,
    285 				    res.result.charmap.codeset);
    286 			else
    287 				charset = nfscmd_insert_charmap(exi, sp, NULL);
    288 		}
    289 		if (charset != NULL)
    290 			ret = NFSCMD_ERR_SUCCESS;
    291 	}
    292 	return (ret);
    293 }
    294 
    295 /*
    296  * nfscmd_convname(addr, export, name, inbound, size)
    297  *
    298  * Convert the given "name" string to the appropriate character set.
    299  * If inbound is true, convert from the client character set to UTF-8.
    300  * If inbound is false, convert from UTF-8 to the client characters set.
    301  */
    302 
    303 char *
    304 nfscmd_convname(struct sockaddr *ca, struct exportinfo *exi, char *name,
    305     int inbound, size_t size)
    306 {
    307 	char *newname;
    308 	char *holdname;
    309 	int err;
    310 	int ret;
    311 	size_t nsize;
    312 	size_t osize;
    313 	struct charset_cache *charset = NULL;
    314 
    315 	charset = nfscmd_findmap(exi, ca);
    316 	if (charset == NULL)
    317 		return (name);
    318 
    319 	/* make sure we have more than enough space */
    320 	newname = kmem_zalloc(size, KM_SLEEP);
    321 	nsize = strlen(name);
    322 	osize = size;
    323 	holdname = newname;
    324 	if (inbound)
    325 		ret = kiconv(charset->inbound, &name, &nsize,
    326 		    &holdname, &osize, &err);
    327 	else
    328 		ret = kiconv(charset->outbound, &name, &nsize,
    329 		    &holdname, &osize, &err);
    330 	if (ret == (size_t)-1) {
    331 		kmem_free(newname, size);
    332 		newname = NULL;
    333 	}
    334 
    335 	return (newname);
    336 }
    337 
    338 /*
    339  * nfscmd_convdirent()
    340  *
    341  * There is only one entry in the data.  Convert to new charset, if
    342  * required and only return a success if it fits.
    343  */
    344 char *
    345 nfscmd_convdirent(struct sockaddr *ca, struct exportinfo *exi, char *data,
    346     size_t size, enum nfsstat3 *error)
    347 {
    348 	char *newdata;
    349 	size_t ret;
    350 	size_t nsize;
    351 	size_t count;
    352 	int err = 0;
    353 	char *iname;
    354 	char *oname;
    355 	struct charset_cache *charset;
    356 
    357 	charset = nfscmd_findmap(exi, ca);
    358 	if (charset == NULL || charset->outbound == (void *)~0)
    359 		return (data);
    360 
    361 	newdata = kmem_zalloc(size, KM_SLEEP);
    362 
    363 	nsize = strlen(((struct dirent64 *)data)->d_name);
    364 	count = size;
    365 	bcopy(data, newdata, sizeof (struct dirent64));
    366 
    367 	iname = ((struct dirent64 *)data)->d_name;
    368 	oname = ((struct dirent64 *)newdata)->d_name;
    369 
    370 	ret = kiconv(charset->outbound, &iname, &nsize, &oname, &count, &err);
    371 	if (ret == (size_t)-1) {
    372 		kmem_free(newdata, size);
    373 		newdata = NULL;
    374 		if (err == E2BIG) {
    375 			if (error != NULL)
    376 				*error = NFS3ERR_NAMETOOLONG;
    377 		} else {
    378 			newdata = data;
    379 		}
    380 	} else {
    381 		ret = strlen(((struct dirent64 *)newdata)->d_name);
    382 		((struct dirent64 *)newdata)->d_reclen =
    383 		    DIRENT64_RECLEN(ret + 1);
    384 	}
    385 	return (newdata);
    386 }
    387 
    388 /*
    389  * nfscmd_convdirplus(addr, export, data, nents, maxsize, ndata)
    390  *
    391  * Convert the dirents in data into a new list of dirents in ndata.
    392  */
    393 
    394 size_t
    395 nfscmd_convdirplus(struct sockaddr *ca, struct exportinfo *exi, char *data,
    396     size_t nents, size_t maxsize, char **ndata)
    397 {
    398 	char *newdata;
    399 	size_t nsize;
    400 	struct dirent64 *dp;
    401 	struct dirent64 *ndp;
    402 	size_t i;
    403 	size_t ret;
    404 	char *iname;
    405 	char *oname;
    406 	size_t ilen;
    407 	size_t olen;
    408 	int err;
    409 	size_t skipped;
    410 	struct charset_cache *charset;
    411 	*ndata = data;	/* return the data if no changes to make */
    412 
    413 	charset = nfscmd_findmap(exi, ca);
    414 
    415 	if (charset == NULL || charset->outbound == (void *)~0)
    416 		return (0);
    417 
    418 	newdata = kmem_zalloc(maxsize, KM_SLEEP);
    419 	nsize = 0;
    420 
    421 	dp = (struct dirent64 *)data;
    422 	ndp = (struct dirent64 *)newdata;
    423 
    424 	for (skipped = 0, i = 0; i < nents; i++) {
    425 		/*
    426 		 * Copy the dp information if it fits. Then copy and
    427 		 * convert the name in the entry.
    428 		 */
    429 		if ((maxsize - nsize) < dp->d_reclen)
    430 			/* doesn't fit */
    431 			break;
    432 		*ndp = *dp;
    433 		iname = dp->d_name;
    434 		ilen = strlen(iname);
    435 		oname = ndp->d_name;
    436 		olen = MIN(MAXNAMELEN, maxsize - nsize);
    437 		ret = kiconv(charset->outbound, &iname, &ilen, &oname,
    438 		    &olen, &err);
    439 
    440 		if (ret == (size_t)-1) {
    441 			switch (err) {
    442 			default:
    443 			case E2BIG:
    444 				break;
    445 			case EILSEQ:
    446 				skipped++;
    447 				dp = nextdp(dp);
    448 				continue;
    449 			}
    450 		}
    451 		ilen = MIN(MAXNAMELEN, maxsize - nsize) - olen;
    452 		ndp->d_name[ilen] = '\0';
    453 		/*
    454 		 * What to do with other errors?
    455 		 * For now, we return the unconverted string.
    456 		 */
    457 		ndp->d_reclen = DIRENT64_RECLEN(strlen(ndp->d_name) + 1);
    458 		nsize += ndp->d_reclen;
    459 		dp = nextdp(dp);
    460 		ndp = nextdp(ndp);
    461 	}
    462 
    463 	*ndata = newdata;
    464 	return (nents - (i + skipped));
    465 }
    466 
    467 /*
    468  * nfscmd_countents(data, len)
    469  *
    470  * How many dirents are there in the data buffer?
    471  */
    472 
    473 size_t
    474 nfscmd_countents(char *data, size_t len)
    475 {
    476 	struct dirent64 *dp = (struct dirent64 *)data;
    477 	size_t curlen;
    478 	size_t reclen;
    479 	size_t nents;
    480 
    481 	for (nents = 0, curlen = 0; curlen < len; curlen += reclen, nents++) {
    482 		reclen = dp->d_reclen;
    483 		dp = nextdp(dp);
    484 	}
    485 	return (nents);
    486 }
    487 
    488 /*
    489  * nfscmd_dropped_entrysize(dir, drop, nents)
    490  *
    491  * We need to drop "drop" entries from dir in order to fit in the
    492  * buffer.  How much do we reduce the overall size by?
    493  */
    494 
    495 size_t
    496 nfscmd_dropped_entrysize(struct dirent64 *dir, size_t drop, size_t nents)
    497 {
    498 	size_t size;
    499 	size_t i;
    500 
    501 	for (i = nents - drop; i > 0 && dir != NULL; i--)
    502 		dir = nextdp(dir);
    503 
    504 	if (dir == NULL)
    505 		return (0);
    506 
    507 	for (size = 0, i = 0; i < drop && dir != NULL; i++) {
    508 		size += dir->d_reclen;
    509 		dir = nextdp(dir);
    510 	}
    511 	return (size);
    512 }
    513