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