1 /* $NetBSD: sysctlfs.c,v 1.10 2009/01/18 10:10:47 lukem 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.10 2009/01/18 10:10:47 lukem 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, i; 162 163 /* 164 * Check if we need to create a new in-memory node or if we 165 * already have one for this path. Shortcut for the rootnode. 166 * Also, memcmp against zero-length would be quite true always. 167 */ 168 if (po->po_len == 0) 169 pn = puffs_getroot(pu); 170 else 171 pn = puffs_pn_nodewalk(pu, puffs_path_walkcmp, po); 172 173 if (pn == NULL) { 174 /* 175 * don't know nodetype? query... 176 * 177 * XXX1: nothing really guarantees 0 is an invalid nodetype 178 * XXX2: is there really no easier way of doing this? we 179 * know the whole mib path 180 */ 181 if (!nodetype) { 182 sname = po->po_path; 183 memcpy(myname, po->po_path, po->po_len * sizeof(int)); 184 185 memset(&qnode, 0, sizeof(qnode)); 186 qnode.sysctl_flags = SYSCTL_VERSION; 187 myname[po->po_len-1] = CTL_QUERY; 188 189 sl = sizeof(sn); 190 if (sysctl(myname, po->po_len, sn, &sl, 191 &qnode, sizeof(qnode)) == -1) 192 abort(); 193 194 for (i = 0; i < sl / sizeof(struct sysctlnode); i++) { 195 if (sn[i].sysctl_num==(*sname)[po->po_len-1]) { 196 nodetype = sn[i].sysctl_flags; 197 break; 198 } 199 } 200 if (!nodetype) 201 return NULL; 202 } 203 204 sfs = emalloc(sizeof(struct sfsnode)); 205 sfs->sysctl_flags = nodetype; 206 sfs->myid = nextid++; 207 208 pn = puffs_pn_new(pu, sfs); 209 assert(pn); 210 } 211 212 return pn; 213 } 214 215 int 216 main(int argc, char *argv[]) 217 { 218 struct puffs_usermount *pu; 219 struct puffs_ops *pops; 220 mntoptparse_t mp; 221 int mntflags, pflags; 222 int detach; 223 int ch; 224 225 setprogname(argv[0]); 226 227 if (argc < 2) 228 errx(1, "usage: %s sysctlfs [-o mntopts] mountpath", 229 getprogname()); 230 231 mntflags = pflags = 0; 232 detach = 1; 233 while ((ch = getopt(argc, argv, "o:s")) != -1) { 234 switch (ch) { 235 case 'o': 236 mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags); 237 if (mp == NULL) 238 err(1, "getmntopts"); 239 freemntopts(mp); 240 break; 241 case 's': 242 detach = 0; 243 break; 244 } 245 } 246 argv += optind; 247 argc -= optind; 248 pflags |= PUFFS_FLAG_BUILDPATH | PUFFS_KFLAG_NOCACHE; 249 250 if (pflags & PUFFS_FLAG_OPDUMP) 251 detach = 0; 252 253 if (argc != 2) 254 errx(1, "usage: %s [-o mntopts] mountpath", getprogname()); 255 256 PUFFSOP_INIT(pops); 257 258 PUFFSOP_SETFSNOP(pops, unmount); 259 PUFFSOP_SETFSNOP(pops, sync); 260 PUFFSOP_SETFSNOP(pops, statvfs); 261 PUFFSOP_SET(pops, sysctlfs, fs, nodetofh); 262 PUFFSOP_SET(pops, sysctlfs, fs, fhtonode); 263 264 PUFFSOP_SET(pops, sysctlfs, node, lookup); 265 PUFFSOP_SET(pops, sysctlfs, node, getattr); 266 PUFFSOP_SET(pops, sysctlfs, node, setattr); 267 PUFFSOP_SET(pops, sysctlfs, node, readdir); 268 PUFFSOP_SET(pops, sysctlfs, node, read); 269 PUFFSOP_SET(pops, sysctlfs, node, write); 270 PUFFSOP_SET(pops, puffs_genfs, node, reclaim); 271 272 pu = puffs_init(pops, _PATH_PUFFS, "sysctlfs", NULL, pflags); 273 if (pu == NULL) 274 err(1, "puffs_init"); 275 276 puffs_set_pathbuild(pu, sysctlfs_pathbuild); 277 puffs_set_pathtransform(pu, sysctlfs_pathtransform); 278 puffs_set_pathcmp(pu, sysctlfs_pathcmp); 279 puffs_set_pathfree(pu, sysctlfs_pathfree); 280 281 puffs_setfhsize(pu, sizeof(struct sfsfid), PUFFS_FHFLAG_NFSV3); 282 283 if (sysctlfs_domount(pu) != 0) 284 errx(1, "domount"); 285 286 if (detach) 287 if (puffs_daemon(pu, 1, 1) == -1) 288 err(1, "puffs_daemon"); 289 290 if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1) 291 err(1, "puffs_mount"); 292 if (puffs_mainloop(pu) == -1) 293 err(1, "mainloop"); 294 295 return 0; 296 } 297 298 static int 299 sysctlfs_domount(struct puffs_usermount *pu) 300 { 301 struct puffs_pathobj *po_root; 302 struct puffs_node *pn_root; 303 struct timeval tv_now; 304 305 rn.myid = 2; 306 rn.sysctl_flags = CTLTYPE_NODE; 307 308 gettimeofday(&tv_now, NULL); 309 TIMEVAL_TO_TIMESPEC(&tv_now, &fstime); 310 311 pn_root = puffs_pn_new(pu, &rn); 312 assert(pn_root != NULL); 313 puffs_setroot(pu, pn_root); 314 315 po_root = puffs_getrootpathobj(pu); 316 po_root->po_path = &sname_root; 317 po_root->po_len = 0; 318 319 fileuid = geteuid(); 320 filegid = getegid(); 321 322 if (fileuid == 0) 323 fileperms = 0755; 324 else 325 fileperms = 0555; 326 327 return 0; 328 } 329 330 int 331 sysctlfs_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize, 332 struct puffs_newinfo *pni) 333 { 334 struct puffs_pathobj po; 335 struct puffs_node *pn; 336 struct sfsnode *sfs; 337 struct sfsfid *sfid; 338 339 sfid = fid; 340 341 po.po_len = sfid->len; 342 po.po_path = &sfid->path; 343 344 pn = getnode(pu, &po, 0); 345 if (pn == NULL) 346 return EINVAL; 347 sfs = pn->pn_data; 348 349 puffs_newinfo_setcookie(pni, pn); 350 if (ISADIR(sfs)) 351 puffs_newinfo_setvtype(pni, VDIR); 352 else 353 puffs_newinfo_setvtype(pni, VREG); 354 355 return 0; 356 } 357 358 int 359 sysctlfs_fs_nodetofh(struct puffs_usermount *pu, void *cookie, 360 void *fid, size_t *fidsize) 361 { 362 struct puffs_node *pn = cookie; 363 struct sfsfid *sfid; 364 365 sfid = fid; 366 sfid->len = PNPLEN(pn); 367 memcpy(&sfid->path, PNPATH(pn), sfid->len * sizeof(int)); 368 369 return 0; 370 } 371 372 static void 373 doprint(struct sfsnode *sfs, struct puffs_pathobj *po, 374 char *buf, size_t bufsize) 375 { 376 size_t sz; 377 378 assert(!ISADIR(sfs)); 379 380 memset(buf, 0, bufsize); 381 switch (SYSCTL_TYPE(sfs->sysctl_flags)) { 382 case CTLTYPE_INT: { 383 int i; 384 sz = sizeof(int); 385 if (sysctl(po->po_path, po->po_len, &i, &sz, NULL, 0) == -1) 386 break; 387 snprintf(buf, bufsize, "%d", i); 388 break; 389 } 390 case CTLTYPE_QUAD: { 391 quad_t q; 392 sz = sizeof(q); 393 if (sysctl(po->po_path, po->po_len, &q, &sz, NULL, 0) == -1) 394 break; 395 snprintf(buf, bufsize, "%" PRId64, q); 396 break; 397 } 398 case CTLTYPE_STRUCT: 399 snprintf(buf, bufsize, "CTLTYPE_STRUCT: implement me and " 400 "score a cookie"); 401 break; 402 case CTLTYPE_STRING: { 403 sz = bufsize; 404 if (sysctl(po->po_path, po->po_len, buf, &sz, NULL, 0) == -1) 405 break; 406 break; 407 } 408 default: 409 snprintf(buf, bufsize, "invalid sysctl CTLTYPE"); 410 break; 411 } 412 } 413 414 static int 415 getlinks(struct sfsnode *sfs, struct puffs_pathobj *po) 416 { 417 struct sysctlnode sn[SFS_NODEPERDIR]; 418 struct sysctlnode qnode; 419 SfsName *sname; 420 size_t sl; 421 422 if (!ISADIR(sfs)) 423 return 1; 424 425 memset(&qnode, 0, sizeof(qnode)); 426 sl = sizeof(sn); 427 qnode.sysctl_flags = SYSCTL_VERSION; 428 sname = po->po_path; 429 (*sname)[po->po_len] = CTL_QUERY; 430 431 if (sysctl(*sname, po->po_len + 1, sn, &sl, 432 &qnode, sizeof(qnode)) == -1) 433 return 0; 434 435 return (sl / sizeof(sn[0])) + 2; 436 } 437 438 static int 439 getsize(struct sfsnode *sfs, struct puffs_pathobj *po) 440 { 441 char buf[SFS_MAXFILE]; 442 443 if (ISADIR(sfs)) 444 return getlinks(sfs, po) * 16; /* totally arbitrary */ 445 446 doprint(sfs, po, buf, sizeof(buf)); 447 return strlen(buf) + 1; 448 } 449 450 int 451 sysctlfs_node_lookup(struct puffs_usermount *pu, void *opc, 452 struct puffs_newinfo *pni, const struct puffs_cn *pcn) 453 { 454 struct puffs_cn *p2cn = __UNCONST(pcn); /* XXX: fix the interface */ 455 struct sysctlnode sn[SFS_NODEPERDIR]; 456 struct sysctlnode qnode; 457 struct puffs_node *pn_dir = opc; 458 struct puffs_node *pn_new; 459 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_new; 460 SfsName *sname = PCNPATH(pcn); 461 size_t sl, i; 462 int nodetype; 463 464 assert(ISADIR(sfs_dir)); 465 466 /* 467 * If we're looking for dotdot, we already have the entire pathname 468 * in sname, courtesy of pathbuild, so we can skip this step. 469 */ 470 if (!PCNISDOTDOT(pcn)) { 471 memset(&qnode, 0, sizeof(qnode)); 472 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 473 qnode.sysctl_flags = SYSCTL_VERSION; 474 (*sname)[PCNPLEN(pcn)] = CTL_QUERY; 475 476 if (sysctl(*sname, PCNPLEN(pcn) + 1, sn, &sl, 477 &qnode, sizeof(qnode)) == -1) 478 return ENOENT; 479 480 for (i = 0; i < sl / sizeof(struct sysctlnode); i++) 481 if (strcmp(sn[i].sysctl_name, pcn->pcn_name) == 0) 482 break; 483 if (i == sl / sizeof(struct sysctlnode)) 484 return ENOENT; 485 486 (*sname)[PCNPLEN(pcn)] = sn[i].sysctl_num; 487 p2cn->pcn_po_full.po_len++; 488 nodetype = sn[i].sysctl_flags; 489 } else 490 nodetype = CTLTYPE_NODE; 491 492 pn_new = getnode(pu, &p2cn->pcn_po_full, nodetype); 493 sfs_new = pn_new->pn_data; 494 495 puffs_newinfo_setcookie(pni, pn_new); 496 if (ISADIR(sfs_new)) 497 puffs_newinfo_setvtype(pni, VDIR); 498 else 499 puffs_newinfo_setvtype(pni, VREG); 500 501 return 0; 502 } 503 504 int 505 sysctlfs_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *va, 506 const struct puffs_cred *pcr) 507 { 508 struct puffs_node *pn = opc; 509 struct sfsnode *sfs = pn->pn_data; 510 511 memset(va, 0, sizeof(struct vattr)); 512 513 if (ISADIR(sfs)) { 514 va->va_type = VDIR; 515 va->va_mode = 0555; 516 } else { 517 va->va_type = VREG; 518 va->va_mode = fileperms; 519 } 520 va->va_uid = fileuid; 521 va->va_gid = filegid; 522 va->va_nlink = getlinks(sfs, &pn->pn_po); 523 va->va_fileid = sfs->myid; 524 va->va_size = getsize(sfs, &pn->pn_po); 525 va->va_gen = 1; 526 va->va_rdev = PUFFS_VNOVAL; 527 va->va_blocksize = 512; 528 va->va_filerev = 1; 529 530 va->va_atime = va->va_mtime = va->va_ctime = va->va_birthtime = fstime; 531 532 return 0; 533 } 534 535 int 536 sysctlfs_node_setattr(struct puffs_usermount *pu, void *opc, 537 const struct vattr *va, const struct puffs_cred *pcr) 538 { 539 540 /* dummy, but required for write */ 541 /* XXX: we could return EOPNOTSUPP or something */ 542 return 0; 543 } 544 545 int 546 sysctlfs_node_readdir(struct puffs_usermount *pu, void *opc, 547 struct dirent *dent, off_t *readoff, size_t *reslen, 548 const struct puffs_cred *pcr, int *eofflag, 549 off_t *cookies, size_t *ncookies) 550 { 551 struct sysctlnode sn[SFS_NODEPERDIR]; 552 struct sysctlnode qnode; 553 struct puffs_node *pn_dir = opc; 554 struct puffs_node *pn_res; 555 struct puffs_pathobj po; 556 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_ent; 557 SfsName *sname; 558 size_t sl, i; 559 enum vtype vt; 560 ino_t id; 561 562 *ncookies = 0; 563 564 again: 565 if (*readoff == DENT_DOT || *readoff == DENT_DOTDOT) { 566 puffs_gendotdent(&dent, sfs_dir->myid, *readoff, reslen); 567 (*readoff)++; 568 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 569 goto again; 570 } 571 572 memset(&qnode, 0, sizeof(qnode)); 573 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 574 qnode.sysctl_flags = SYSCTL_VERSION; 575 sname = PNPATH(pn_dir); 576 (*sname)[PNPLEN(pn_dir)] = CTL_QUERY; 577 578 if (sysctl(*sname, PNPLEN(pn_dir) + 1, sn, &sl, 579 &qnode, sizeof(qnode)) == -1) 580 return ENOENT; 581 582 po.po_path = sname; 583 po.po_len = PNPLEN(pn_dir)+1; 584 585 for (i = DENT_ADJ(*readoff); i < sl / sizeof(struct sysctlnode); i++) { 586 if (SYSCTL_TYPE(sn[i].sysctl_flags) == CTLTYPE_NODE) 587 vt = VDIR; 588 else 589 vt = VREG; 590 591 /* 592 * check if the node exists. if so, give it the real 593 * inode number. otherwise just fake it. 594 */ 595 (*sname)[PNPLEN(pn_dir)] = sn[i].sysctl_num; 596 pn_res = puffs_pn_nodewalk(pu, puffs_path_walkcmp, &po); 597 if (pn_res) { 598 sfs_ent = pn_res->pn_data; 599 id = sfs_ent->myid; 600 } else { 601 id = nextid++; 602 } 603 604 if (!puffs_nextdent(&dent, sn[i].sysctl_name, id, 605 puffs_vtype2dt(vt), reslen)) 606 return 0; 607 608 (*readoff)++; 609 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 610 } 611 612 *eofflag = 1; 613 return 0; 614 } 615 616 int 617 sysctlfs_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf, 618 off_t offset, size_t *resid, const struct puffs_cred *pcr, 619 int ioflag) 620 { 621 char localbuf[SFS_MAXFILE]; 622 struct puffs_node *pn = opc; 623 struct sfsnode *sfs = pn->pn_data; 624 int xfer; 625 626 if (ISADIR(sfs)) 627 return EISDIR; 628 629 doprint(sfs, &pn->pn_po, localbuf, sizeof(localbuf)); 630 xfer = MIN(*resid, strlen(localbuf) - offset); 631 632 if (xfer <= 0) 633 return 0; 634 635 memcpy(buf, localbuf + offset, xfer); 636 *resid -= xfer; 637 638 if (*resid) { 639 buf[xfer] = '\n'; 640 (*resid)--; 641 } 642 643 return 0; 644 } 645 646 int 647 sysctlfs_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf, 648 off_t offset, size_t *resid, const struct puffs_cred *cred, 649 int ioflag) 650 { 651 struct puffs_node *pn = opc; 652 struct sfsnode *sfs = pn->pn_data; 653 long long ll; 654 int i, rv; 655 656 if (puffs_cred_isjuggernaut(cred) == 0) 657 return EACCES; 658 659 if (ISADIR(sfs)) 660 return EISDIR; 661 662 if (offset != 0) 663 return EINVAL; 664 665 if (ioflag & PUFFS_IO_APPEND) 666 return EINVAL; 667 668 switch (SYSCTL_TYPE(sfs->sysctl_flags)) { 669 case CTLTYPE_INT: 670 if (sscanf((const char *)buf, "%d", &i) != 1) 671 return EINVAL; 672 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 673 &i, sizeof(int)); 674 break; 675 case CTLTYPE_QUAD: 676 if (sscanf((const char *)buf, "%lld", &ll) != 1) 677 return EINVAL; 678 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 679 &ll, sizeof(long long)); 680 break; 681 case CTLTYPE_STRING: 682 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, buf, *resid); 683 break; 684 default: 685 rv = EINVAL; 686 break; 687 } 688 689 if (rv) 690 return rv; 691 692 *resid = 0; 693 return 0; 694 } 695