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 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 78 nfscmd_init(void) 79 { 80 zone_key_create(&nfscmd_zone_key, nfscmd_zone_init, 81 NULL, nfscmd_zone_fini); 82 } 83 84 void 85 nfscmd_fini(void) 86 { 87 (void) zone_key_delete(nfscmd_zone_key); 88 } 89 90 /*ARGSUSED*/ 91 static void * 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 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 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 * 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 * 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 * 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 * 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 * 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 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 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 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