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
nfscmd_args(uint_t did)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
nfscmd_init(void)66 nfscmd_init(void)
67 {
68 mutex_init(&nfscmd_lock, NULL, MUTEX_DEFAULT, NULL);
69 }
70
71 void
nfscmd_fini(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
nfscmd_send(nfscmd_arg_t * arg,nfscmd_res_t * res)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 *
nfscmd_findmap(struct exportinfo * exi,struct sockaddr * sp)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 *
nfscmd_insert_charmap(struct exportinfo * exi,struct sockaddr * sp,char * name)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 *
nfscmd_charmap(exportinfo_t * exi,struct sockaddr * sp)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 *
nfscmd_convname(struct sockaddr * ca,struct exportinfo * exi,char * name,int inbound,size_t size)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 *
nfscmd_convdirent(struct sockaddr * ca,struct exportinfo * exi,char * data,size_t size,enum nfsstat3 * error)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
nfscmd_convdirplus(struct sockaddr * ca,struct exportinfo * exi,char * data,size_t nents,size_t maxsize,char ** ndata)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
nfscmd_countents(char * data,size_t len)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
nfscmd_dropped_entrysize(struct dirent64 * dir,size_t drop,size_t nents)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