1 /* $NetBSD: sysctlfs.c,v 1.8 2007/11/30 19:02:41 pooka Exp $ */ 2 3 /* 4 * Copyright (c) 2006, 2007 Antti Kantee. All Rights Reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 /* 29 * sysctlfs: mount sysctls as a file system tree. Supports query and 30 * modify of nodes in the sysctl namespace in addition to namespace 31 * traversal. 32 */ 33 34 #include <sys/cdefs.h> 35 #ifndef lint 36 __RCSID("$NetBSD: sysctlfs.c,v 1.8 2007/11/30 19:02:41 pooka Exp $"); 37 #endif /* !lint */ 38 39 #include <sys/types.h> 40 #include <sys/sysctl.h> 41 42 #include <assert.h> 43 #include <err.h> 44 #include <errno.h> 45 #include <mntopts.h> 46 #include <paths.h> 47 #include <puffs.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <unistd.h> 51 #include <util.h> 52 53 PUFFSOP_PROTOS(sysctlfs) 54 55 struct sfsnode { 56 int sysctl_flags; 57 ino_t myid; 58 }; 59 60 #define SFSPATH_DOTDOT 0 61 #define SFSPATH_NORMAL 1 62 63 #define N_HIERARCHY 10 64 typedef int SfsName[N_HIERARCHY]; 65 66 struct sfsfid { 67 int len; 68 SfsName path; 69 }; 70 71 static struct sfsnode rn; 72 static SfsName sname_root; 73 static struct timespec fstime; 74 75 static ino_t nextid = 3; 76 static mode_t fileperms; 77 static uid_t fileuid; 78 static gid_t filegid; 79 80 #define ISADIR(a) ((SYSCTL_TYPE(a->sysctl_flags) == CTLTYPE_NODE)) 81 #define SFS_MAXFILE 8192 82 #define SFS_NODEPERDIR 128 83 84 static int sysctlfs_domount(struct puffs_usermount *); 85 86 /* 87 * build paths. doesn't support rename (but neither does the fs) 88 */ 89 static int 90 sysctlfs_pathbuild(struct puffs_usermount *pu, 91 const struct puffs_pathobj *parent, const struct puffs_pathobj *comp, 92 size_t offset, struct puffs_pathobj *res) 93 { 94 SfsName *sname; 95 size_t clen; 96 97 assert(parent->po_len < N_HIERARCHY); /* code uses +1 */ 98 99 sname = malloc(sizeof(SfsName)); 100 assert(sname != NULL); 101 102 clen = parent->po_len; 103 if (comp->po_len == SFSPATH_DOTDOT) { 104 assert(clen != 0); 105 clen--; 106 } 107 108 memcpy(sname, parent->po_path, clen * sizeof(int)); 109 110 res->po_path = sname; 111 res->po_len = clen; 112 113 return 0; 114 } 115 116 static int 117 sysctlfs_pathtransform(struct puffs_usermount *pu, 118 const struct puffs_pathobj *p, const const struct puffs_cn *pcn, 119 struct puffs_pathobj *res) 120 { 121 122 res->po_path = NULL; 123 /* 124 * XXX: overload. prevents us from doing rename, but the fs 125 * (and sysctl(3)) doesn't support it, so no biggie 126 */ 127 if (PCNISDOTDOT(pcn)) { 128 res->po_len = SFSPATH_DOTDOT; 129 }else { 130 res->po_len = SFSPATH_NORMAL; 131 } 132 133 return 0; 134 } 135 136 static int 137 sysctlfs_pathcmp(struct puffs_usermount *pu, struct puffs_pathobj *po1, 138 struct puffs_pathobj *po2, size_t clen, int checkprefix) 139 { 140 141 if (memcmp(po1->po_path, po2->po_path, clen * sizeof(int)) == 0) 142 return 0; 143 return 1; 144 } 145 146 static void 147 sysctlfs_pathfree(struct puffs_usermount *pu, struct puffs_pathobj *po) 148 { 149 150 free(po->po_path); 151 } 152 153 static struct puffs_node * 154 getnode(struct puffs_usermount *pu, struct puffs_pathobj *po, int nodetype) 155 { 156 struct sysctlnode sn[SFS_NODEPERDIR]; 157 struct sysctlnode qnode; 158 struct puffs_node *pn; 159 struct sfsnode *sfs; 160 SfsName myname, *sname; 161 size_t sl; 162 int i; 163 164 /* 165 * Check if we need to create a new in-memory node or if we 166 * already have one for this path. Shortcut for the rootnode. 167 * Also, memcmp against zero-length would be quite true always. 168 */ 169 if (po->po_len == 0) 170 pn = puffs_getroot(pu); 171 else 172 pn = puffs_pn_nodewalk(pu, puffs_path_walkcmp, po); 173 174 if (pn == NULL) { 175 /* 176 * don't know nodetype? query... 177 * 178 * XXX1: nothing really guarantees 0 is an invalid nodetype 179 * XXX2: is there really no easier way of doing this? we 180 * know the whole mib path 181 */ 182 if (!nodetype) { 183 sname = po->po_path; 184 memcpy(myname, po->po_path, po->po_len * sizeof(int)); 185 186 memset(&qnode, 0, sizeof(qnode)); 187 qnode.sysctl_flags = SYSCTL_VERSION; 188 myname[po->po_len-1] = CTL_QUERY; 189 190 sl = sizeof(sn); 191 if (sysctl(myname, po->po_len, sn, &sl, 192 &qnode, sizeof(qnode)) == -1) 193 abort(); 194 195 for (i = 0; i < sl / sizeof(struct sysctlnode); i++) { 196 if (sn[i].sysctl_num==(*sname)[po->po_len-1]) { 197 nodetype = sn[i].sysctl_flags; 198 break; 199 } 200 } 201 if (!nodetype) 202 return NULL; 203 } 204 205 sfs = emalloc(sizeof(struct sfsnode)); 206 sfs->sysctl_flags = nodetype; 207 sfs->myid = nextid++; 208 209 pn = puffs_pn_new(pu, sfs); 210 assert(pn); 211 } 212 213 return pn; 214 } 215 216 int 217 main(int argc, char *argv[]) 218 { 219 struct puffs_usermount *pu; 220 struct puffs_ops *pops; 221 mntoptparse_t mp; 222 int mntflags, pflags; 223 int detach; 224 int ch; 225 226 setprogname(argv[0]); 227 228 if (argc < 2) 229 errx(1, "usage: %s sysctlfs [-o mntopts]�mountpath", 230 getprogname()); 231 232 mntflags = pflags = 0; 233 detach = 1; 234 while ((ch = getopt(argc, argv, "o:s")) != -1) { 235 switch (ch) { 236 case 'o': 237 mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags); 238 if (mp == NULL) 239 err(1, "getmntopts"); 240 freemntopts(mp); 241 break; 242 case 's': 243 detach = 0; 244 break; 245 } 246 } 247 argv += optind; 248 argc -= optind; 249 pflags |= PUFFS_FLAG_BUILDPATH | PUFFS_KFLAG_NOCACHE; 250 251 if (pflags & PUFFS_FLAG_OPDUMP) 252 detach = 0; 253 254 if (argc != 2) 255 errx(1, "usage: %s [-o mntopts]�mountpath", getprogname()); 256 257 PUFFSOP_INIT(pops); 258 259 PUFFSOP_SETFSNOP(pops, unmount); 260 PUFFSOP_SETFSNOP(pops, sync); 261 PUFFSOP_SETFSNOP(pops, statvfs); 262 PUFFSOP_SET(pops, sysctlfs, fs, nodetofh); 263 PUFFSOP_SET(pops, sysctlfs, fs, fhtonode); 264 265 PUFFSOP_SET(pops, sysctlfs, node, lookup); 266 PUFFSOP_SET(pops, sysctlfs, node, getattr); 267 PUFFSOP_SET(pops, sysctlfs, node, setattr); 268 PUFFSOP_SET(pops, sysctlfs, node, readdir); 269 PUFFSOP_SET(pops, sysctlfs, node, read); 270 PUFFSOP_SET(pops, sysctlfs, node, write); 271 PUFFSOP_SET(pops, puffs_genfs, node, reclaim); 272 273 pu = puffs_init(pops, _PATH_PUFFS, "sysctlfs", NULL, pflags); 274 if (pu == NULL) 275 err(1, "puffs_init"); 276 277 puffs_set_pathbuild(pu, sysctlfs_pathbuild); 278 puffs_set_pathtransform(pu, sysctlfs_pathtransform); 279 puffs_set_pathcmp(pu, sysctlfs_pathcmp); 280 puffs_set_pathfree(pu, sysctlfs_pathfree); 281 282 puffs_setfhsize(pu, sizeof(struct sfsfid), PUFFS_FHFLAG_NFSV3); 283 284 if (sysctlfs_domount(pu) != 0) 285 errx(1, "domount"); 286 287 if (detach) 288 if (puffs_daemon(pu, 1, 1) == -1) 289 err(1, "puffs_daemon"); 290 291 if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1) 292 err(1, "puffs_mount"); 293 if (puffs_mainloop(pu) == -1) 294 err(1, "mainloop"); 295 296 return 0; 297 } 298 299 static int 300 sysctlfs_domount(struct puffs_usermount *pu) 301 { 302 struct puffs_pathobj *po_root; 303 struct puffs_node *pn_root; 304 struct timeval tv_now; 305 306 rn.myid = 2; 307 rn.sysctl_flags = CTLTYPE_NODE; 308 309 gettimeofday(&tv_now, NULL); 310 TIMEVAL_TO_TIMESPEC(&tv_now, &fstime); 311 312 pn_root = puffs_pn_new(pu, &rn); 313 assert(pn_root != NULL); 314 puffs_setroot(pu, pn_root); 315 316 po_root = puffs_getrootpathobj(pu); 317 po_root->po_path = &sname_root; 318 po_root->po_len = 0; 319 320 fileuid = geteuid(); 321 filegid = getegid(); 322 323 if (fileuid == 0) 324 fileperms = 0755; 325 else 326 fileperms = 0555; 327 328 return 0; 329 } 330 331 int 332 sysctlfs_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize, 333 struct puffs_newinfo *pni) 334 { 335 struct puffs_pathobj po; 336 struct puffs_node *pn; 337 struct sfsnode *sfs; 338 struct sfsfid *sfid; 339 340 sfid = fid; 341 342 po.po_len = sfid->len; 343 po.po_path = &sfid->path; 344 345 pn = getnode(pu, &po, 0); 346 if (pn == NULL) 347 return EINVAL; 348 sfs = pn->pn_data; 349 350 puffs_newinfo_setcookie(pni, pn); 351 if (ISADIR(sfs)) 352 puffs_newinfo_setvtype(pni, VDIR); 353 else 354 puffs_newinfo_setvtype(pni, VREG); 355 356 return 0; 357 } 358 359 int 360 sysctlfs_fs_nodetofh(struct puffs_usermount *pu, void *cookie, 361 void *fid, size_t *fidsize) 362 { 363 struct puffs_node *pn = cookie; 364 struct sfsfid *sfid; 365 366 sfid = fid; 367 sfid->len = PNPLEN(pn); 368 memcpy(&sfid->path, PNPATH(pn), sfid->len * sizeof(int)); 369 370 return 0; 371 } 372 373 static void 374 doprint(struct sfsnode *sfs, struct puffs_pathobj *po, 375 char *buf, size_t bufsize) 376 { 377 size_t sz; 378 379 assert(!ISADIR(sfs)); 380 381 memset(buf, 0, bufsize); 382 switch (SYSCTL_TYPE(sfs->sysctl_flags)) { 383 case CTLTYPE_INT: { 384 int i; 385 sz = sizeof(int); 386 if (sysctl(po->po_path, po->po_len, &i, &sz, NULL, 0) == -1) 387 break; 388 snprintf(buf, bufsize, "%d", i); 389 break; 390 } 391 case CTLTYPE_QUAD: { 392 quad_t q; 393 sz = sizeof(q); 394 if (sysctl(po->po_path, po->po_len, &q, &sz, NULL, 0) == -1) 395 break; 396 snprintf(buf, bufsize, "%" PRId64, q); 397 break; 398 } 399 case CTLTYPE_STRUCT: 400 snprintf(buf, bufsize, "CTLTYPE_STRUCT: implement me and " 401 "score a cookie"); 402 break; 403 case CTLTYPE_STRING: { 404 sz = bufsize; 405 if (sysctl(po->po_path, po->po_len, buf, &sz, NULL, 0) == -1) 406 break; 407 break; 408 } 409 default: 410 snprintf(buf, bufsize, "invalid sysctl CTLTYPE"); 411 break; 412 } 413 } 414 415 static int 416 getlinks(struct sfsnode *sfs, struct puffs_pathobj *po) 417 { 418 struct sysctlnode sn[SFS_NODEPERDIR]; 419 struct sysctlnode qnode; 420 SfsName *sname; 421 size_t sl; 422 423 if (!ISADIR(sfs)) 424 return 1; 425 426 memset(&qnode, 0, sizeof(qnode)); 427 sl = sizeof(sn); 428 qnode.sysctl_flags = SYSCTL_VERSION; 429 sname = po->po_path; 430 (*sname)[po->po_len] = CTL_QUERY; 431 432 if (sysctl(*sname, po->po_len + 1, sn, &sl, 433 &qnode, sizeof(qnode)) == -1) 434 return 0; 435 436 return (sl / sizeof(sn[0])) + 2; 437 } 438 439 static int 440 getsize(struct sfsnode *sfs, struct puffs_pathobj *po) 441 { 442 char buf[SFS_MAXFILE]; 443 444 if (ISADIR(sfs)) 445 return getlinks(sfs, po) * 16; /* totally arbitrary */ 446 447 doprint(sfs, po, buf, sizeof(buf)); 448 return strlen(buf) + 1; 449 } 450 451 int 452 sysctlfs_node_lookup(struct puffs_usermount *pu, void *opc, 453 struct puffs_newinfo *pni, const struct puffs_cn *pcn) 454 { 455 struct puffs_cn *p2cn = __UNCONST(pcn); /* XXX: fix the interface */ 456 struct sysctlnode sn[SFS_NODEPERDIR]; 457 struct sysctlnode qnode; 458 struct puffs_node *pn_dir = opc; 459 struct puffs_node *pn_new; 460 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_new; 461 SfsName *sname = PCNPATH(pcn); 462 size_t sl; 463 int i, nodetype; 464 465 assert(ISADIR(sfs_dir)); 466 467 /* 468 * If we're looking for dotdot, we already have the entire pathname 469 * in sname, courtesy of pathbuild, so we can skip this step. 470 */ 471 if (!PCNISDOTDOT(pcn)) { 472 memset(&qnode, 0, sizeof(qnode)); 473 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 474 qnode.sysctl_flags = SYSCTL_VERSION; 475 (*sname)[PCNPLEN(pcn)] = CTL_QUERY; 476 477 if (sysctl(*sname, PCNPLEN(pcn) + 1, sn, &sl, 478 &qnode, sizeof(qnode)) == -1) 479 return ENOENT; 480 481 for (i = 0; i < sl / sizeof(struct sysctlnode); i++) 482 if (strcmp(sn[i].sysctl_name, pcn->pcn_name) == 0) 483 break; 484 if (i == sl / sizeof(struct sysctlnode)) 485 return ENOENT; 486 487 (*sname)[PCNPLEN(pcn)] = sn[i].sysctl_num; 488 p2cn->pcn_po_full.po_len++; 489 nodetype = sn[i].sysctl_flags; 490 } else 491 nodetype = CTLTYPE_NODE; 492 493 pn_new = getnode(pu, &p2cn->pcn_po_full, nodetype); 494 sfs_new = pn_new->pn_data; 495 496 puffs_newinfo_setcookie(pni, pn_new); 497 if (ISADIR(sfs_new)) 498 puffs_newinfo_setvtype(pni, VDIR); 499 else 500 puffs_newinfo_setvtype(pni, VREG); 501 502 return 0; 503 } 504 505 int 506 sysctlfs_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *va, 507 const struct puffs_cred *pcr) 508 { 509 struct puffs_node *pn = opc; 510 struct sfsnode *sfs = pn->pn_data; 511 512 memset(va, 0, sizeof(struct vattr)); 513 514 if (ISADIR(sfs)) { 515 va->va_type = VDIR; 516 va->va_mode = 0555; 517 } else { 518 va->va_type = VREG; 519 va->va_mode = fileperms; 520 } 521 va->va_uid = fileuid; 522 va->va_gid = filegid; 523 va->va_nlink = getlinks(sfs, &pn->pn_po); 524 va->va_fileid = sfs->myid; 525 va->va_size = getsize(sfs, &pn->pn_po); 526 va->va_gen = 1; 527 va->va_rdev = PUFFS_VNOVAL; 528 va->va_blocksize = 512; 529 va->va_filerev = 1; 530 531 va->va_atime = va->va_mtime = va->va_ctime = va->va_birthtime = fstime; 532 533 return 0; 534 } 535 536 int 537 sysctlfs_node_setattr(struct puffs_usermount *pu, void *opc, 538 const struct vattr *va, const struct puffs_cred *pcr) 539 { 540 541 /* dummy, but required for write */ 542 /* XXX: we could return EOPNOTSUPP or something */ 543 return 0; 544 } 545 546 int 547 sysctlfs_node_readdir(struct puffs_usermount *pu, void *opc, 548 struct dirent *dent, off_t *readoff, size_t *reslen, 549 const struct puffs_cred *pcr, int *eofflag, 550 off_t *cookies, size_t *ncookies) 551 { 552 struct sysctlnode sn[SFS_NODEPERDIR]; 553 struct sysctlnode qnode; 554 struct puffs_node *pn_dir = opc; 555 struct puffs_node *pn_res; 556 struct puffs_pathobj po; 557 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_ent; 558 SfsName *sname; 559 size_t sl; 560 enum vtype vt; 561 ino_t id; 562 int i; 563 564 *ncookies = 0; 565 566 again: 567 if (*readoff == DENT_DOT || *readoff == DENT_DOTDOT) { 568 puffs_gendotdent(&dent, sfs_dir->myid, *readoff, reslen); 569 (*readoff)++; 570 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 571 goto again; 572 } 573 574 memset(&qnode, 0, sizeof(qnode)); 575 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 576 qnode.sysctl_flags = SYSCTL_VERSION; 577 sname = PNPATH(pn_dir); 578 (*sname)[PNPLEN(pn_dir)] = CTL_QUERY; 579 580 if (sysctl(*sname, PNPLEN(pn_dir) + 1, sn, &sl, 581 &qnode, sizeof(qnode)) == -1) 582 return ENOENT; 583 584 po.po_path = sname; 585 po.po_len = PNPLEN(pn_dir)+1; 586 587 for (i = DENT_ADJ(*readoff); i < sl / sizeof(struct sysctlnode); i++) { 588 if (SYSCTL_TYPE(sn[i].sysctl_flags) == CTLTYPE_NODE) 589 vt = VDIR; 590 else 591 vt = VREG; 592 593 /* 594 * check if the node exists. if so, give it the real 595 * inode number. otherwise just fake it. 596 */ 597 (*sname)[PNPLEN(pn_dir)] = sn[i].sysctl_num; 598 pn_res = puffs_pn_nodewalk(pu, puffs_path_walkcmp, &po); 599 if (pn_res) { 600 sfs_ent = pn_res->pn_data; 601 id = sfs_ent->myid; 602 } else { 603 id = nextid++; 604 } 605 606 if (!puffs_nextdent(&dent, sn[i].sysctl_name, id, 607 puffs_vtype2dt(vt), reslen)) 608 return 0; 609 610 (*readoff)++; 611 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 612 } 613 614 *eofflag = 1; 615 return 0; 616 } 617 618 int 619 sysctlfs_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf, 620 off_t offset, size_t *resid, const struct puffs_cred *pcr, 621 int ioflag) 622 { 623 char localbuf[SFS_MAXFILE]; 624 struct puffs_node *pn = opc; 625 struct sfsnode *sfs = pn->pn_data; 626 int xfer; 627 628 if (ISADIR(sfs)) 629 return EISDIR; 630 631 doprint(sfs, &pn->pn_po, localbuf, sizeof(localbuf)); 632 xfer = MIN(*resid, strlen(localbuf) - offset); 633 634 if (xfer <= 0) 635 return 0; 636 637 memcpy(buf, localbuf + offset, xfer); 638 *resid -= xfer; 639 640 if (*resid) { 641 buf[xfer] = '\n'; 642 (*resid)--; 643 } 644 645 return 0; 646 } 647 648 int 649 sysctlfs_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf, 650 off_t offset, size_t *resid, const struct puffs_cred *cred, 651 int ioflag) 652 { 653 struct puffs_node *pn = opc; 654 struct sfsnode *sfs = pn->pn_data; 655 long long ll; 656 int i, rv; 657 658 if (puffs_cred_isjuggernaut(cred) == 0) 659 return EACCES; 660 661 if (ISADIR(sfs)) 662 return EISDIR; 663 664 if (offset != 0) 665 return EINVAL; 666 667 if (ioflag & PUFFS_IO_APPEND) 668 return EINVAL; 669 670 switch (SYSCTL_TYPE(sfs->sysctl_flags)) { 671 case CTLTYPE_INT: 672 if (sscanf((const char *)buf, "%d", &i) != 1) 673 return EINVAL; 674 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 675 &i, sizeof(int)); 676 break; 677 case CTLTYPE_QUAD: 678 if (sscanf((const char *)buf, "%lld", &ll) != 1) 679 return EINVAL; 680 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 681 &ll, sizeof(long long)); 682 break; 683 case CTLTYPE_STRING: 684 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, buf, *resid); 685 break; 686 default: 687 rv = EINVAL; 688 break; 689 } 690 691 if (rv) 692 return rv; 693 694 *resid = 0; 695 return 0; 696 } 697