xref: /illumos-gate/usr/src/uts/common/fs/nfs/nfs_cmd.c (revision 45d2468cd430f160914c353c714144054804373a)
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