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