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