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