1 /* $NetBSD: sysctlfs.c,v 1.5 2007/11/05 17:54:32 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.5 2007/11/05 17:54:32 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 (daemon(1, 1) == -1) 289 err(1, "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_cc *pcc, 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(puffs_cc_getusermount(pcc), &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_cc *pcc, 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_cc *pcc, void *opc, struct puffs_newinfo *pni, 453 const struct puffs_cn *pcn) 454 { 455 struct puffs_usermount *pu = puffs_cc_getusermount(pcc); 456 struct puffs_cn *p2cn = __UNCONST(pcn); /* XXX: fix the interface */ 457 struct sysctlnode sn[SFS_NODEPERDIR]; 458 struct sysctlnode qnode; 459 struct puffs_node *pn_dir = opc; 460 struct puffs_node *pn_new; 461 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_new; 462 SfsName *sname = PCNPATH(pcn); 463 size_t sl; 464 int i, nodetype; 465 466 assert(ISADIR(sfs_dir)); 467 468 /* 469 * If we're looking for dotdot, we already have the entire pathname 470 * in sname, courtesy of pathbuild, so we can skip this step. 471 */ 472 if (!PCNISDOTDOT(pcn)) { 473 memset(&qnode, 0, sizeof(qnode)); 474 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 475 qnode.sysctl_flags = SYSCTL_VERSION; 476 (*sname)[PCNPLEN(pcn)] = CTL_QUERY; 477 478 if (sysctl(*sname, PCNPLEN(pcn) + 1, sn, &sl, 479 &qnode, sizeof(qnode)) == -1) 480 return ENOENT; 481 482 for (i = 0; i < sl / sizeof(struct sysctlnode); i++) 483 if (strcmp(sn[i].sysctl_name, pcn->pcn_name) == 0) 484 break; 485 if (i == sl / sizeof(struct sysctlnode)) 486 return ENOENT; 487 488 (*sname)[PCNPLEN(pcn)] = sn[i].sysctl_num; 489 p2cn->pcn_po_full.po_len++; 490 nodetype = sn[i].sysctl_flags; 491 } else 492 nodetype = CTLTYPE_NODE; 493 494 pn_new = getnode(pu, &p2cn->pcn_po_full, nodetype); 495 sfs_new = pn_new->pn_data; 496 497 puffs_newinfo_setcookie(pni, pn_new); 498 if (ISADIR(sfs_new)) 499 puffs_newinfo_setvtype(pni, VDIR); 500 else 501 puffs_newinfo_setvtype(pni, VREG); 502 503 return 0; 504 } 505 506 int 507 sysctlfs_node_getattr(struct puffs_cc *pcc, void *opc, struct vattr *va, 508 const struct puffs_cred *pcr, const struct puffs_cid *pcid) 509 { 510 struct puffs_node *pn = opc; 511 struct sfsnode *sfs = pn->pn_data; 512 513 memset(va, 0, sizeof(struct vattr)); 514 515 if (ISADIR(sfs)) { 516 va->va_type = VDIR; 517 va->va_mode = 0555; 518 } else { 519 va->va_type = VREG; 520 va->va_mode = fileperms; 521 } 522 va->va_uid = fileuid; 523 va->va_gid = filegid; 524 va->va_nlink = getlinks(sfs, &pn->pn_po); 525 va->va_fileid = sfs->myid; 526 va->va_size = getsize(sfs, &pn->pn_po); 527 va->va_gen = 1; 528 va->va_rdev = PUFFS_VNOVAL; 529 va->va_blocksize = 512; 530 va->va_filerev = 1; 531 532 va->va_atime = va->va_mtime = va->va_ctime = va->va_birthtime = fstime; 533 534 return 0; 535 } 536 537 int 538 sysctlfs_node_setattr(struct puffs_cc *pcc, void *opc, 539 const struct vattr *va, const struct puffs_cred *pcr, 540 const struct puffs_cid *pcid) 541 { 542 543 /* dummy, but required for write */ 544 /* XXX: we could return EOPNOTSUPP or something */ 545 return 0; 546 } 547 548 int 549 sysctlfs_node_readdir(struct puffs_cc *pcc, void *opc, struct dirent *dent, 550 off_t *readoff, size_t *reslen, const struct puffs_cred *pcr, 551 int *eofflag, off_t *cookies, size_t *ncookies) 552 { 553 struct puffs_usermount *pu = puffs_cc_getusermount(pcc); 554 struct sysctlnode sn[SFS_NODEPERDIR]; 555 struct sysctlnode qnode; 556 struct puffs_node *pn_dir = opc; 557 struct puffs_node *pn_res; 558 struct puffs_pathobj po; 559 struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_ent; 560 SfsName *sname; 561 size_t sl; 562 enum vtype vt; 563 ino_t id; 564 int i; 565 566 *ncookies = 0; 567 568 again: 569 if (*readoff == DENT_DOT || *readoff == DENT_DOTDOT) { 570 puffs_gendotdent(&dent, sfs_dir->myid, *readoff, reslen); 571 (*readoff)++; 572 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 573 goto again; 574 } 575 576 memset(&qnode, 0, sizeof(qnode)); 577 sl = SFS_NODEPERDIR * sizeof(struct sysctlnode); 578 qnode.sysctl_flags = SYSCTL_VERSION; 579 sname = PNPATH(pn_dir); 580 (*sname)[PNPLEN(pn_dir)] = CTL_QUERY; 581 582 if (sysctl(*sname, PNPLEN(pn_dir) + 1, sn, &sl, 583 &qnode, sizeof(qnode)) == -1) 584 return ENOENT; 585 586 po.po_path = sname; 587 po.po_len = PNPLEN(pn_dir)+1; 588 589 for (i = DENT_ADJ(*readoff); i < sl / sizeof(struct sysctlnode); i++) { 590 if (SYSCTL_TYPE(sn[i].sysctl_flags) == CTLTYPE_NODE) 591 vt = VDIR; 592 else 593 vt = VREG; 594 595 /* 596 * check if the node exists. if so, give it the real 597 * inode number. otherwise just fake it. 598 */ 599 (*sname)[PNPLEN(pn_dir)] = sn[i].sysctl_num; 600 pn_res = puffs_pn_nodewalk(pu, puffs_path_walkcmp, &po); 601 if (pn_res) { 602 sfs_ent = pn_res->pn_data; 603 id = sfs_ent->myid; 604 } else { 605 id = nextid++; 606 } 607 608 if (!puffs_nextdent(&dent, sn[i].sysctl_name, id, 609 puffs_vtype2dt(vt), reslen)) 610 return 0; 611 612 (*readoff)++; 613 PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff); 614 } 615 616 *eofflag = 1; 617 return 0; 618 } 619 620 int 621 sysctlfs_node_read(struct puffs_cc *pcc, void *opc, uint8_t *buf, 622 off_t offset, size_t *resid, const struct puffs_cred *pcr, 623 int ioflag) 624 { 625 char localbuf[SFS_MAXFILE]; 626 struct puffs_node *pn = opc; 627 struct sfsnode *sfs = pn->pn_data; 628 int xfer; 629 630 if (ISADIR(sfs)) 631 return EISDIR; 632 633 doprint(sfs, &pn->pn_po, localbuf, sizeof(localbuf)); 634 xfer = MIN(*resid, strlen(localbuf) - offset); 635 636 if (xfer <= 0) 637 return 0; 638 639 memcpy(buf, localbuf + offset, xfer); 640 *resid -= xfer; 641 642 if (*resid) { 643 buf[xfer] = '\n'; 644 (*resid)--; 645 } 646 647 return 0; 648 } 649 650 int 651 sysctlfs_node_write(struct puffs_cc *pcc, void *opc, uint8_t *buf, 652 off_t offset, size_t *resid, const struct puffs_cred *cred, 653 int ioflag) 654 { 655 struct puffs_node *pn = opc; 656 struct sfsnode *sfs = pn->pn_data; 657 long long ll; 658 int i, rv; 659 660 if (puffs_cred_isjuggernaut(cred) == 0) 661 return EACCES; 662 663 if (ISADIR(sfs)) 664 return EISDIR; 665 666 if (offset != 0) 667 return EINVAL; 668 669 if (ioflag & PUFFS_IO_APPEND) 670 return EINVAL; 671 672 switch (SYSCTL_TYPE(sfs->sysctl_flags)) { 673 case CTLTYPE_INT: 674 if (sscanf((const char *)buf, "%d", &i) != 1) 675 return EINVAL; 676 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 677 &i, sizeof(int)); 678 break; 679 case CTLTYPE_QUAD: 680 if (sscanf((const char *)buf, "%lld", &ll) != 1) 681 return EINVAL; 682 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, 683 &ll, sizeof(long long)); 684 break; 685 case CTLTYPE_STRING: 686 rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, buf, *resid); 687 break; 688 default: 689 rv = EINVAL; 690 break; 691 } 692 693 if (rv) 694 return rv; 695 696 *resid = 0; 697 return 0; 698 } 699