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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * rmtab.c 24 * 25 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 26 * Use is subject to license terms. 27 */ 28 29 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <ctype.h> 34 #include <sys/types.h> 35 #include <string.h> 36 #include <sys/param.h> 37 #include <sys/stat.h> 38 #include <sys/file.h> 39 #include <sys/time.h> 40 #include <errno.h> 41 #include <rpcsvc/mount.h> 42 #include <sys/pathconf.h> 43 #include <sys/systeminfo.h> 44 #include <sys/utsname.h> 45 #include <signal.h> 46 #include <locale.h> 47 #include <unistd.h> 48 #include <thread.h> 49 #include <syslog.h> 50 #include <sys/socket.h> 51 #include <netinet/in.h> 52 #include <arpa/inet.h> 53 #include "../lib/sharetab.h" 54 #include "hashset.h" 55 #include "mountd.h" 56 57 static char RMTAB[] = "/etc/rmtab"; 58 static FILE *rmtabf = NULL; 59 60 /* 61 * There is nothing magic about the value selected here. Too low, 62 * and mountd might spend too much time rewriting the rmtab file. 63 * Too high, it won't do it frequently enough. 64 */ 65 static int rmtab_del_thresh = 250; 66 67 #define RMTAB_TOOMANY_DELETED() \ 68 ((rmtab_deleted > rmtab_del_thresh) && (rmtab_deleted > rmtab_inuse)) 69 70 /* 71 * mountd's version of a "struct mountlist". It is the same except 72 * for the added ml_pos field. 73 */ 74 struct mntentry { 75 char *m_host; 76 char *m_path; 77 long m_pos; 78 }; 79 80 static HASHSET mntlist; 81 82 static int mntentry_equal(const void *, const void *); 83 static uint32_t mntentry_hash(const void *); 84 static int mntlist_contains(char *, char *); 85 static void rmtab_delete(long); 86 static long rmtab_insert(char *, char *); 87 static void rmtab_rewrite(void); 88 static void rmtab_parse(char *buf); 89 static bool_t xdr_mntlistencode(XDR * xdrs, HASHSET * mntlist); 90 91 #define exstrdup(s) \ 92 strcpy(exmalloc(strlen(s)+1), s) 93 94 95 static int rmtab_inuse; 96 static int rmtab_deleted; 97 98 static rwlock_t rmtab_lock; /* lock to protect rmtab list */ 99 100 101 /* 102 * Check whether the given client/path combination 103 * already appears in the mount list. 104 */ 105 106 static int 107 mntlist_contains(char *host, char *path) 108 { 109 struct mntentry m; 110 111 m.m_host = host; 112 m.m_path = path; 113 114 return (h_get(mntlist, &m) != NULL); 115 } 116 117 118 /* 119 * Add an entry to the mount list. 120 * First check whether it's there already - the client 121 * may have crashed and be rebooting. 122 */ 123 124 static void 125 mntlist_insert(char *host, char *path) 126 { 127 if (!mntlist_contains(host, path)) { 128 struct mntentry *m; 129 130 m = exmalloc(sizeof (struct mntentry)); 131 132 m->m_host = exstrdup(host); 133 m->m_path = exstrdup(path); 134 m->m_pos = rmtab_insert(host, path); 135 (void) h_put(mntlist, m); 136 } 137 } 138 139 void 140 mntlist_new(char *host, char *path) 141 { 142 (void) rw_wrlock(&rmtab_lock); 143 mntlist_insert(host, path); 144 (void) rw_unlock(&rmtab_lock); 145 } 146 147 /* 148 * Delete an entry from the mount list. 149 */ 150 151 void 152 mntlist_delete(char *host, char *path) 153 { 154 struct mntentry *m, mm; 155 156 mm.m_host = host; 157 mm.m_path = path; 158 159 (void) rw_wrlock(&rmtab_lock); 160 161 if (m = (struct mntentry *)h_get(mntlist, &mm)) { 162 rmtab_delete(m->m_pos); 163 164 (void) h_delete(mntlist, m); 165 166 free(m->m_path); 167 free(m->m_host); 168 free(m); 169 170 if (RMTAB_TOOMANY_DELETED()) 171 rmtab_rewrite(); 172 } 173 (void) rw_unlock(&rmtab_lock); 174 } 175 176 /* 177 * Delete all entries for a host from the mount list 178 */ 179 180 void 181 mntlist_delete_all(char *host) 182 { 183 HASHSET_ITERATOR iterator; 184 struct mntentry *m; 185 186 (void) rw_wrlock(&rmtab_lock); 187 188 iterator = h_iterator(mntlist); 189 190 while (m = (struct mntentry *)h_next(iterator)) { 191 if (strcasecmp(m->m_host, host)) 192 continue; 193 194 rmtab_delete(m->m_pos); 195 196 (void) h_delete(mntlist, m); 197 198 free(m->m_path); 199 free(m->m_host); 200 free(m); 201 } 202 203 if (RMTAB_TOOMANY_DELETED()) 204 rmtab_rewrite(); 205 206 (void) rw_unlock(&rmtab_lock); 207 208 if (iterator != NULL) 209 free(iterator); 210 } 211 212 /* 213 * Equivalent to xdr_mountlist from librpcsvc but for HASHSET 214 * rather that for a linked list. It is used only to encode data 215 * from HASHSET before sending it over the wire. 216 */ 217 218 static bool_t 219 xdr_mntlistencode(XDR *xdrs, HASHSET *mntlist) 220 { 221 HASHSET_ITERATOR iterator = h_iterator(*mntlist); 222 223 for (;;) { 224 struct mntentry *m = (struct mntentry *)h_next(iterator); 225 bool_t more_data = (m != NULL); 226 227 if (!xdr_bool(xdrs, &more_data)) { 228 if (iterator != NULL) 229 free(iterator); 230 return (FALSE); 231 } 232 233 if (!more_data) 234 break; 235 236 if ((!xdr_name(xdrs, &m->m_host)) || 237 (!xdr_dirpath(xdrs, &m->m_path))) { 238 if (iterator != NULL) 239 free(iterator); 240 return (FALSE); 241 } 242 243 } 244 245 if (iterator != NULL) 246 free(iterator); 247 248 return (TRUE); 249 } 250 251 void 252 mntlist_send(SVCXPRT *transp) 253 { 254 (void) rw_rdlock(&rmtab_lock); 255 256 errno = 0; 257 if (!svc_sendreply(transp, xdr_mntlistencode, (char *)&mntlist)) 258 log_cant_reply(transp); 259 260 (void) rw_unlock(&rmtab_lock); 261 } 262 263 /* 264 * Compute a hash for an mntlist entry. 265 */ 266 #define SPACE (uchar_t)0x20 267 #define HASH_BITS 3 /* Num. of bits to shift the hash */ 268 #define HASH_MAXHOST 6 /* Max. number of characters from a hostname */ 269 #define HASH_MAXPATH 3 /* Max. number of characters from a pathname */ 270 271 /* 272 * Compute a 32 bit hash value for an mntlist entry. 273 * We consider only first HASH_MAXHOST characters from the host part. 274 * We skip the first character in the path part (usually /), and we 275 * consider at most HASH_MAXPATH following characters. 276 * We shift the hash value by HASH_BITS after each character. 277 */ 278 279 static uint32_t 280 mntentry_hash(const void *p) 281 { 282 struct mntentry *m = (struct mntentry *)p; 283 uchar_t *s; 284 uint_t i, sum = 0; 285 286 for (i = 0, s = (uchar_t *)m->m_host; *s && i < HASH_MAXHOST; i++) { 287 uchar_t ls = tolower(*s); 288 sum <<= HASH_BITS; 289 sum += ls - SPACE; 290 *s++; 291 } 292 293 /* 294 * The first character is usually '/'. 295 * Start with the next character. 296 */ 297 for (i = 0, s = (uchar_t *)m->m_path+1; *s && i < HASH_MAXPATH; i++) { 298 sum <<= HASH_BITS; 299 sum += *s++ - SPACE; 300 } 301 302 return (sum); 303 } 304 305 /* 306 * Compare mntlist entries. 307 * The comparison ignores a value of m_pos. 308 */ 309 310 static int 311 mntentry_equal(const void *p1, const void *p2) 312 { 313 struct mntentry *m1 = (struct mntentry *)p1; 314 struct mntentry *m2 = (struct mntentry *)p2; 315 316 return ((strcasecmp(m1->m_host, m2->m_host) || 317 strcmp(m1->m_path, m2->m_path)) ? 0 : 1); 318 } 319 320 /* 321 * Rewrite /etc/rmtab with a current content of mntlist. 322 */ 323 static void 324 rmtab_rewrite() 325 { 326 if (rmtabf) 327 (void) fclose(rmtabf); 328 329 /* Rewrite the file. */ 330 if (rmtabf = fopen(RMTAB, "w+")) { 331 HASHSET_ITERATOR iterator; 332 struct mntentry *m; 333 334 (void) fchmod(fileno(rmtabf), 335 (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)); 336 rmtab_inuse = rmtab_deleted = 0; 337 338 iterator = h_iterator(mntlist); 339 340 while (m = (struct mntentry *)h_next(iterator)) 341 m->m_pos = rmtab_insert(m->m_host, m->m_path); 342 if (iterator != NULL) 343 free(iterator); 344 } 345 } 346 347 /* 348 * Parse the content of /etc/rmtab and insert the entries into mntlist. 349 * The buffer s should be ended with a NUL char. 350 */ 351 352 static void 353 rmtab_parse(char *s) 354 { 355 char *host; 356 char *path; 357 char *tmp; 358 struct in6_addr ipv6addr; 359 360 host_part: 361 if (*s == '#') 362 goto skip_rest; 363 364 host = s; 365 for (;;) { 366 switch (*s++) { 367 case '\0': 368 return; 369 case '\n': 370 goto host_part; 371 case ':': 372 s[-1] = '\0'; 373 goto path_part; 374 case '[': 375 tmp = strchr(s, ']'); 376 if (tmp) { 377 *tmp = '\0'; 378 if (inet_pton(AF_INET6, s, &ipv6addr) > 0) { 379 host = s; 380 s = ++tmp; 381 } else 382 *tmp = ']'; 383 } 384 default: 385 continue; 386 } 387 } 388 389 path_part: 390 path = s; 391 for (;;) { 392 switch (*s++) { 393 case '\n': 394 s[-1] = '\0'; 395 if (*host && *path) 396 mntlist_insert(host, path); 397 goto host_part; 398 case '\0': 399 if (*host && *path) 400 mntlist_insert(host, path); 401 return; 402 default: 403 continue; 404 } 405 } 406 407 skip_rest: 408 for (;;) { 409 switch (*++s) { 410 case '\n': 411 goto host_part; 412 case '\0': 413 return; 414 default: 415 continue; 416 } 417 } 418 } 419 420 /* 421 * Read in contents of rmtab. 422 * Call rmtab_parse to parse the file and store entries in mntlist. 423 * Rewrites the file to get rid of unused entries. 424 */ 425 426 #define RMTAB_LOADLEN (16*2024) /* Max bytes to read at a time */ 427 428 void 429 rmtab_load() 430 { 431 FILE *fp; 432 433 (void) rwlock_init(&rmtab_lock, USYNC_THREAD, NULL); 434 435 /* 436 * Don't need to lock the list at this point 437 * because there's only a single thread running. 438 */ 439 mntlist = h_create(mntentry_hash, mntentry_equal, 101, 0.75); 440 441 if (fp = fopen(RMTAB, "r")) { 442 char buf[RMTAB_LOADLEN+1]; 443 size_t len; 444 445 /* 446 * Read at most RMTAB_LOADLEN bytes from /etc/rmtab. 447 * - if fread returns RMTAB_LOADLEN we can be in the middle 448 * of a line so change the last newline character into NUL 449 * and seek back to the next character after newline. 450 * - otherwise set NUL behind the last character read. 451 */ 452 while ((len = fread(buf, 1, RMTAB_LOADLEN, fp)) > 0) { 453 if (len == RMTAB_LOADLEN) { 454 int i; 455 456 for (i = 1; i < len; i++) { 457 if (buf[len-i] == '\n') { 458 buf[len-i] = '\0'; 459 (void) fseek(fp, -i+1, 460 SEEK_CUR); 461 goto parse; 462 } 463 } 464 } 465 466 /* Put a NUL character at the end of buffer */ 467 buf[len] = '\0'; 468 parse: 469 rmtab_parse(buf); 470 } 471 (void) fclose(fp); 472 } 473 rmtab_rewrite(); 474 } 475 476 /* 477 * Write an entry at the current location in rmtab 478 * for the given client and path. 479 * 480 * Returns the starting position of the entry 481 * or -1 if there was an error. 482 */ 483 484 long 485 rmtab_insert(char *host, char *path) 486 { 487 long pos; 488 struct in6_addr ipv6addr; 489 490 if (rmtabf == NULL || fseek(rmtabf, 0L, 2) == -1) { 491 return (-1); 492 } 493 pos = ftell(rmtabf); 494 495 /* 496 * Check if host is an IPv6 literal 497 */ 498 499 if (inet_pton(AF_INET6, host, &ipv6addr) > 0) { 500 if (fprintf(rmtabf, "[%s]:%s\n", host, path) == EOF) { 501 return (-1); 502 } 503 } else { 504 if (fprintf(rmtabf, "%s:%s\n", host, path) == EOF) { 505 return (-1); 506 } 507 } 508 if (fflush(rmtabf) == EOF) { 509 return (-1); 510 } 511 rmtab_inuse++; 512 return (pos); 513 } 514 515 /* 516 * Mark as unused the rmtab entry at the given offset in the file. 517 */ 518 519 void 520 rmtab_delete(long pos) 521 { 522 if (rmtabf != NULL && pos != -1 && fseek(rmtabf, pos, 0) == 0) { 523 (void) fprintf(rmtabf, "#"); 524 (void) fflush(rmtabf); 525 526 rmtab_inuse--; 527 rmtab_deleted++; 528 } 529 } 530