1 #include "stdinc.h" 2 3 #include "9.h" 4 5 enum { 6 NUserHash = 1009, 7 }; 8 9 typedef struct Ubox Ubox; 10 typedef struct User User; 11 12 typedef struct User { 13 char* uid; 14 char* uname; 15 char* leader; 16 char** group; 17 int ngroup; 18 19 User* next; /* */ 20 User* ihash; /* lookup by .uid */ 21 User* nhash; /* lookup by .uname */ 22 } User; 23 24 #pragma varargck type "U" User* 25 26 typedef struct Ubox { 27 User* head; 28 User* tail; 29 int nuser; 30 int len; 31 32 User* ihash[NUserHash]; /* lookup by .uid */ 33 User* nhash[NUserHash]; /* lookup by .uname */ 34 } Ubox; 35 36 static struct { 37 VtLock* lock; 38 39 Ubox* box; 40 } ubox; 41 42 static char usersDefault[] = { 43 "adm:adm:adm:sys\n" 44 "none:none::\n" 45 "noworld:noworld::\n" 46 "sys:sys::\n" 47 "glenda:glenda:glenda:\n" 48 }; 49 50 static char* usersMandatory[] = { 51 "adm", 52 "none", 53 "noworld", 54 "sys", 55 nil, 56 }; 57 58 char* uidadm = "adm"; 59 char* unamenone = "none"; 60 char* uidnoworld = "noworld"; 61 62 static u32int 63 userHash(char* s) 64 { 65 uchar *p; 66 u32int hash; 67 68 hash = 0; 69 for(p = (uchar*)s; *p != '\0'; p++) 70 hash = hash*7 + *p; 71 72 return hash % NUserHash; 73 } 74 75 static User* 76 _userByUid(Ubox* box, char* uid) 77 { 78 User *u; 79 80 if(box != nil){ 81 for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){ 82 if(strcmp(u->uid, uid) == 0) 83 return u; 84 } 85 } 86 vtSetError("uname: uid '%s' not found", uid); 87 return nil; 88 } 89 90 char* 91 unameByUid(char* uid) 92 { 93 User *u; 94 char *uname; 95 96 vtRLock(ubox.lock); 97 if((u = _userByUid(ubox.box, uid)) == nil){ 98 vtRUnlock(ubox.lock); 99 return nil; 100 } 101 uname = vtStrDup(u->uname); 102 vtRUnlock(ubox.lock); 103 104 return uname; 105 } 106 107 static User* 108 _userByUname(Ubox* box, char* uname) 109 { 110 User *u; 111 112 if(box != nil){ 113 for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){ 114 if(strcmp(u->uname, uname) == 0) 115 return u; 116 } 117 } 118 vtSetError("uname: uname '%s' not found", uname); 119 return nil; 120 } 121 122 char* 123 uidByUname(char* uname) 124 { 125 User *u; 126 char *uid; 127 128 vtRLock(ubox.lock); 129 if((u = _userByUname(ubox.box, uname)) == nil){ 130 vtRUnlock(ubox.lock); 131 return nil; 132 } 133 uid = vtStrDup(u->uid); 134 vtRUnlock(ubox.lock); 135 136 return uid; 137 } 138 139 static int 140 _groupMember(Ubox* box, char* group, char* member, int whenNoGroup) 141 { 142 int i; 143 User *g, *m; 144 145 /* 146 * Is 'member' a member of 'group'? 147 * Note that 'group' is a 'uid' and not a 'uname'. 148 * A 'member' is automatically in their own group. 149 */ 150 if((g = _userByUid(box, group)) == nil) 151 return whenNoGroup; 152 if((m = _userByUname(box, member)) == nil) 153 return 0; 154 if(m == g) 155 return 1; 156 for(i = 0; i < g->ngroup; i++){ 157 if(strcmp(g->group[i], member) == 0) 158 return 1; 159 } 160 return 0; 161 } 162 163 int 164 groupWriteMember(char* uname) 165 { 166 int ret; 167 168 /* 169 * If there is a ``write'' group, then only its members can write 170 * to the file system, no matter what the permission bits say. 171 * 172 * To users not in the ``write'' group, the file system appears 173 * read only. This is used to serve sources.cs.bell-labs.com 174 * to the world. 175 * 176 * Note that if there is no ``write'' group, then this routine 177 * makes it look like everyone is a member -- the opposite 178 * of what groupMember does. 179 * 180 * We use this for sources.cs.bell-labs.com. 181 * If this slows things down too much on systems that don't 182 * use this functionality, we could cache the write group lookup. 183 */ 184 185 vtRLock(ubox.lock); 186 ret = _groupMember(ubox.box, "write", uname, 1); 187 vtRUnlock(ubox.lock); 188 return ret; 189 } 190 191 static int 192 _groupRemMember(Ubox* box, User* g, char* member) 193 { 194 int i; 195 196 if(_userByUname(box, member) == nil) 197 return 0; 198 199 for(i = 0; i < g->ngroup; i++){ 200 if(strcmp(g->group[i], member) == 0) 201 break; 202 } 203 if(i >= g->ngroup){ 204 if(strcmp(g->uname, member) == 0) 205 vtSetError("uname: '%s' always in own group", member); 206 else 207 vtSetError("uname: '%s' not in group '%s'", 208 member, g->uname); 209 return 0; 210 } 211 212 vtMemFree(g->group[i]); 213 214 box->len -= strlen(member); 215 if(g->ngroup > 1) 216 box->len--; 217 g->ngroup--; 218 switch(g->ngroup){ 219 case 0: 220 vtMemFree(g->group); 221 g->group = nil; 222 break; 223 default: 224 while(i < g->ngroup){ 225 g->group[i] = g->group[i+1]; 226 i++; 227 } 228 /*FALLTHROUGH*/ 229 case 1: 230 g->group = vtMemRealloc(g->group, (g->ngroup)*sizeof(char*)); 231 break; 232 } 233 234 return 1; 235 } 236 237 static int 238 _groupAddMember(Ubox* box, User* g, char* member) 239 { 240 User *u; 241 242 if((u = _userByUname(box, member)) == nil) 243 return 0; 244 if(_groupMember(box, g->uid, u->uname, 0)){ 245 if(strcmp(g->uname, member) == 0) 246 vtSetError("uname: '%s' always in own group", member); 247 else 248 vtSetError("uname: '%s' already in group '%s'", 249 member, g->uname); 250 return 0; 251 } 252 253 g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*)); 254 g->group[g->ngroup] = vtStrDup(member); 255 box->len += strlen(member); 256 g->ngroup++; 257 if(g->ngroup > 1) 258 box->len++; 259 260 return 1; 261 } 262 263 int 264 groupMember(char* group, char* member) 265 { 266 int r; 267 268 if(group == nil) 269 return 0; 270 271 vtRLock(ubox.lock); 272 r = _groupMember(ubox.box, group, member, 0); 273 vtRUnlock(ubox.lock); 274 275 return r; 276 } 277 278 int 279 groupLeader(char* group, char* member) 280 { 281 int r; 282 User *g; 283 284 /* 285 * Is 'member' the leader of 'group'? 286 * Note that 'group' is a 'uid' and not a 'uname'. 287 * Uname 'none' cannot be a group leader. 288 */ 289 if(strcmp(member, unamenone) == 0 || group == nil) 290 return 0; 291 292 vtRLock(ubox.lock); 293 if((g = _userByUid(ubox.box, group)) == nil){ 294 vtRUnlock(ubox.lock); 295 return 0; 296 } 297 if(g->leader != nil){ 298 if(strcmp(g->leader, member) == 0){ 299 vtRUnlock(ubox.lock); 300 return 1; 301 } 302 r = 0; 303 } 304 else 305 r = _groupMember(ubox.box, group, member, 0); 306 vtRUnlock(ubox.lock); 307 308 return r; 309 } 310 311 static void 312 userFree(User* u) 313 { 314 int i; 315 316 vtMemFree(u->uid); 317 vtMemFree(u->uname); 318 if(u->leader != nil) 319 vtMemFree(u->leader); 320 if(u->ngroup){ 321 for(i = 0; i < u->ngroup; i++) 322 vtMemFree(u->group[i]); 323 vtMemFree(u->group); 324 } 325 vtMemFree(u); 326 } 327 328 static User* 329 userAlloc(char* uid, char* uname) 330 { 331 User *u; 332 333 u = vtMemAllocZ(sizeof(User)); 334 u->uid = vtStrDup(uid); 335 u->uname = vtStrDup(uname); 336 337 return u; 338 } 339 340 int 341 validUserName(char* name) 342 { 343 Rune *r; 344 static Rune invalid[] = L"#:,()"; 345 346 for(r = invalid; *r != '\0'; r++){ 347 if(utfrune(name, *r)) 348 return 0; 349 } 350 return 1; 351 } 352 353 static int 354 userFmt(Fmt* fmt) 355 { 356 User *u; 357 int i, r; 358 359 u = va_arg(fmt->args, User*); 360 361 r = fmtprint(fmt, "%s:%s:", u->uid, u->uname); 362 if(u->leader != nil) 363 r += fmtprint(fmt, u->leader); 364 r += fmtprint(fmt, ":"); 365 if(u->ngroup){ 366 r += fmtprint(fmt, u->group[0]); 367 for(i = 1; i < u->ngroup; i++) 368 r += fmtprint(fmt, ",%s", u->group[i]); 369 } 370 371 return r; 372 } 373 374 static int 375 usersFileWrite(Ubox* box) 376 { 377 Fs *fs; 378 User *u; 379 int i, r; 380 Fsys *fsys; 381 char *p, *q, *s; 382 File *dir, *file; 383 384 if((fsys = fsysGet("main")) == nil) 385 return 0; 386 fsysFsRlock(fsys); 387 fs = fsysGetFs(fsys); 388 389 /* 390 * BUG: 391 * the owner/group/permissions need to be thought out. 392 */ 393 r = 0; 394 if((dir = fileOpen(fs, "/active")) == nil) 395 goto tidy0; 396 if((file = fileWalk(dir, "adm")) == nil) 397 file = fileCreate(dir, "adm", ModeDir|0775, uidadm); 398 fileDecRef(dir); 399 if(file == nil) 400 goto tidy; 401 dir = file; 402 if((file = fileWalk(dir, "users")) == nil) 403 file = fileCreate(dir, "users", 0664, uidadm); 404 fileDecRef(dir); 405 if(file == nil) 406 goto tidy; 407 if(!fileTruncate(file, uidadm)) 408 goto tidy; 409 410 p = s = vtMemAlloc(box->len+1); 411 q = p + box->len+1; 412 for(u = box->head; u != nil; u = u->next){ 413 p += snprint(p, q-p, "%s:%s:", u->uid, u->uname); 414 if(u->leader != nil) 415 p+= snprint(p, q-p, u->leader); 416 p += snprint(p, q-p, ":"); 417 if(u->ngroup){ 418 p += snprint(p, q-p, u->group[0]); 419 for(i = 1; i < u->ngroup; i++) 420 p += snprint(p, q-p, ",%s", u->group[i]); 421 } 422 p += snprint(p, q-p, "\n"); 423 } 424 r = fileWrite(file, s, box->len, 0, uidadm); 425 vtMemFree(s); 426 427 tidy: 428 if(file != nil) 429 fileDecRef(file); 430 tidy0: 431 fsysFsRUnlock(fsys); 432 fsysPut(fsys); 433 434 return r; 435 } 436 437 static void 438 uboxRemUser(Ubox* box, User *u) 439 { 440 User **h, *up; 441 442 h = &box->ihash[userHash(u->uid)]; 443 for(up = *h; up != nil && up != u; up = up->ihash) 444 h = &up->ihash; 445 assert(up == u); 446 *h = up->ihash; 447 box->len -= strlen(u->uid); 448 449 h = &box->nhash[userHash(u->uname)]; 450 for(up = *h; up != nil && up != u; up = up->nhash) 451 h = &up->nhash; 452 assert(up == u); 453 *h = up->nhash; 454 box->len -= strlen(u->uname); 455 456 h = &box->head; 457 for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next) 458 h = &up->next; 459 assert(up == u); 460 *h = u->next; 461 u->next = nil; 462 463 box->len -= 4; 464 box->nuser--; 465 } 466 467 static void 468 uboxAddUser(Ubox* box, User* u) 469 { 470 User **h, *up; 471 472 h = &box->ihash[userHash(u->uid)]; 473 u->ihash = *h; 474 *h = u; 475 box->len += strlen(u->uid); 476 477 h = &box->nhash[userHash(u->uname)]; 478 u->nhash = *h; 479 *h = u; 480 box->len += strlen(u->uname); 481 482 h = &box->head; 483 for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next) 484 h = &up->next; 485 u->next = *h; 486 *h = u; 487 488 box->len += 4; 489 box->nuser++; 490 } 491 492 static void 493 uboxDump(Ubox* box) 494 { 495 User* u; 496 497 consPrint("nuser %d len = %d\n", box->nuser, box->len); 498 499 for(u = box->head; u != nil; u = u->next) 500 consPrint("%U\n", u); 501 } 502 503 static void 504 uboxFree(Ubox* box) 505 { 506 User *next, *u; 507 508 for(u = box->head; u != nil; u = next){ 509 next = u->next; 510 userFree(u); 511 } 512 vtMemFree(box); 513 } 514 515 static int 516 uboxInit(char* users, int len) 517 { 518 User *g, *u; 519 Ubox *box, *obox; 520 int blank, comment, i, nuser; 521 char *buf, *f[5], **line, *p, *q, *s; 522 523 /* 524 * Strip out whitespace and comments. 525 * Note that comments are pointless, they disappear 526 * when the server writes the database back out. 527 */ 528 blank = 1; 529 comment = nuser = 0; 530 531 s = p = buf = vtMemAlloc(len+1); 532 for(q = users; *q != '\0'; q++){ 533 if(*q == '\r' || *q == '\t' || *q == ' ') 534 continue; 535 if(*q == '\n'){ 536 if(!blank){ 537 if(p != s){ 538 *p++ = '\n'; 539 nuser++; 540 s = p; 541 } 542 blank = 1; 543 } 544 comment = 0; 545 continue; 546 } 547 if(*q == '#') 548 comment = 1; 549 blank = 0; 550 if(!comment) 551 *p++ = *q; 552 } 553 *p = '\0'; 554 555 line = vtMemAllocZ((nuser+2)*sizeof(char*)); 556 if((i = gettokens(buf, line, nuser+2, "\n")) != nuser){ 557 fprint(2, "nuser %d (%d) botch\n", nuser, i); 558 vtMemFree(line); 559 vtMemFree(buf); 560 return 0; 561 } 562 563 // fprint(2, "nuser %d\n", nuser); 564 565 /* 566 * Everything is updated in a local Ubox until verified. 567 */ 568 box = vtMemAllocZ(sizeof(Ubox)); 569 570 /* 571 * First pass - check format, check for duplicates 572 * and enter in hash buckets. 573 */ 574 for(i = 0; i < nuser; i++){ 575 s = vtStrDup(line[i]); 576 if(getfields(s, f, nelem(f), 0, ":") != 4){ 577 fprint(2, "bad line '%s'\n", line[i]); 578 vtMemFree(s); 579 continue; 580 } 581 if(*f[0] == '\0' || *f[1] == '\0'){ 582 fprint(2, "bad line '%s'\n", line[i]); 583 vtMemFree(s); 584 continue; 585 } 586 if(!validUserName(f[0])){ 587 fprint(2, "invalid uid '%s'\n", f[0]); 588 vtMemFree(s); 589 continue; 590 } 591 if(_userByUid(box, f[0]) != nil){ 592 fprint(2, "duplicate uid '%s'\n", f[0]); 593 vtMemFree(s); 594 continue; 595 } 596 if(!validUserName(f[1])){ 597 fprint(2, "invalid uname '%s'\n", f[0]); 598 vtMemFree(s); 599 continue; 600 } 601 if(_userByUname(box, f[1]) != nil){ 602 fprint(2, "duplicate uname '%s'\n", f[1]); 603 vtMemFree(s); 604 continue; 605 } 606 607 u = userAlloc(f[0], f[1]); 608 uboxAddUser(box, u); 609 610 vtMemFree(s); 611 } 612 assert(box->nuser == nuser); 613 614 /* 615 * Second pass - fill in leader and group information. 616 */ 617 for(i = 0; i < nuser; i++){ 618 s = vtStrDup(line[i]); 619 getfields(s, f, nelem(f), 0, ":"); 620 621 assert(g = _userByUname(box, f[1])); 622 if(*f[2] != '\0'){ 623 if((u = _userByUname(box, f[2])) == nil) 624 g->leader = vtStrDup(g->uname); 625 else 626 g->leader = vtStrDup(u->uname); 627 box->len += strlen(g->leader); 628 } 629 for(p = f[3]; p != nil; p = q){ 630 if((q = utfrune(p, L',')) != nil) 631 *q++ = '\0'; 632 if(!_groupAddMember(box, g, p)){ 633 // print/log error here 634 } 635 } 636 637 vtMemFree(s); 638 } 639 640 vtMemFree(line); 641 vtMemFree(buf); 642 643 for(i = 0; usersMandatory[i] != nil; i++){ 644 if((u = _userByUid(box, usersMandatory[i])) == nil){ 645 vtSetError("user '%s' is mandatory", usersMandatory[i]); 646 uboxFree(box); 647 return 0; 648 } 649 if(strcmp(u->uid, u->uname) != 0){ 650 vtSetError("uid/uname for user '%s' must match", 651 usersMandatory[i]); 652 uboxFree(box); 653 return 0; 654 } 655 } 656 657 vtLock(ubox.lock); 658 obox = ubox.box; 659 ubox.box = box; 660 vtUnlock(ubox.lock); 661 662 if(obox != nil) 663 uboxFree(obox); 664 665 return 1; 666 } 667 668 int 669 usersFileRead(char* path) 670 { 671 char *p; 672 File *file; 673 Fsys *fsys; 674 int len, r; 675 uvlong size; 676 677 if((fsys = fsysGet("main")) == nil) 678 return 0; 679 fsysFsRlock(fsys); 680 681 if(path == nil) 682 path = "/active/adm/users"; 683 684 r = 0; 685 if((file = fileOpen(fsysGetFs(fsys), path)) != nil){ 686 if(fileGetSize(file, &size)){ 687 len = size; 688 p = vtMemAlloc(size+1); 689 if(fileRead(file, p, len, 0) == len){ 690 p[len] = '\0'; 691 r = uboxInit(p, len); 692 } 693 } 694 fileDecRef(file); 695 } 696 697 fsysFsRUnlock(fsys); 698 fsysPut(fsys); 699 700 return r; 701 } 702 703 static int 704 cmdUname(int argc, char* argv[]) 705 { 706 User *u, *up; 707 int d, dflag, i, r; 708 char *p, *uid, *uname; 709 char *createfmt = "fsys main create /active/usr/%s %s %s d775"; 710 char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]"; 711 712 dflag = 0; 713 714 ARGBEGIN{ 715 default: 716 return cliError(usage); 717 case 'd': 718 dflag = 1; 719 break; 720 }ARGEND 721 722 if(argc < 1){ 723 if(!dflag) 724 return cliError(usage); 725 vtRLock(ubox.lock); 726 uboxDump(ubox.box); 727 vtRUnlock(ubox.lock); 728 return 1; 729 } 730 731 uname = argv[0]; 732 argc--; argv++; 733 734 if(argc == 0){ 735 vtRLock(ubox.lock); 736 if((u = _userByUname(ubox.box, uname)) == nil){ 737 vtRUnlock(ubox.lock); 738 return 0; 739 } 740 consPrint("\t%U\n", u); 741 vtRUnlock(ubox.lock); 742 return 1; 743 } 744 745 vtLock(ubox.lock); 746 u = _userByUname(ubox.box, uname); 747 while(argc--){ 748 if(argv[0][0] == '%'){ 749 if(u == nil){ 750 vtUnlock(ubox.lock); 751 return 0; 752 } 753 p = &argv[0][1]; 754 if((up = _userByUname(ubox.box, p)) != nil){ 755 vtSetError("uname: uname '%s' already exists", 756 up->uname); 757 vtUnlock(ubox.lock); 758 return 0; 759 } 760 for(i = 0; usersMandatory[i] != nil; i++){ 761 if(strcmp(usersMandatory[i], uname) != 0) 762 continue; 763 vtSetError("uname: uname '%s' is mandatory", 764 uname); 765 vtUnlock(ubox.lock); 766 return 0; 767 } 768 769 d = strlen(p) - strlen(u->uname); 770 for(up = ubox.box->head; up != nil; up = up->next){ 771 if(up->leader != nil){ 772 if(strcmp(up->leader, u->uname) == 0){ 773 vtMemFree(up->leader); 774 up->leader = vtStrDup(p); 775 ubox.box->len += d; 776 } 777 } 778 for(i = 0; i < up->ngroup; i++){ 779 if(strcmp(up->group[i], u->uname) != 0) 780 continue; 781 vtMemFree(up->group[i]); 782 up->group[i] = vtStrDup(p); 783 ubox.box->len += d; 784 break; 785 } 786 } 787 788 uboxRemUser(ubox.box, u); 789 vtMemFree(u->uname); 790 u->uname = vtStrDup(p); 791 uboxAddUser(ubox.box, u); 792 } 793 else if(argv[0][0] == '='){ 794 if(u == nil){ 795 vtUnlock(ubox.lock); 796 return 0; 797 } 798 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 799 if(argv[0][1] != '\0'){ 800 vtUnlock(ubox.lock); 801 return 0; 802 } 803 } 804 if(u->leader != nil){ 805 ubox.box->len -= strlen(u->leader); 806 vtMemFree(u->leader); 807 u->leader = nil; 808 } 809 if(up != nil){ 810 u->leader = vtStrDup(up->uname); 811 ubox.box->len += strlen(u->leader); 812 } 813 } 814 else if(argv[0][0] == '+'){ 815 if(u == nil){ 816 vtUnlock(ubox.lock); 817 return 0; 818 } 819 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 820 vtUnlock(ubox.lock); 821 return 0; 822 } 823 if(!_groupAddMember(ubox.box, u, up->uname)){ 824 vtUnlock(ubox.lock); 825 return 0; 826 } 827 } 828 else if(argv[0][0] == '-'){ 829 if(u == nil){ 830 vtUnlock(ubox.lock); 831 return 0; 832 } 833 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 834 vtUnlock(ubox.lock); 835 return 0; 836 } 837 if(!_groupRemMember(ubox.box, u, up->uname)){ 838 vtUnlock(ubox.lock); 839 return 0; 840 } 841 } 842 else{ 843 if(u != nil){ 844 vtSetError("uname: uname '%s' already exists", 845 u->uname); 846 vtUnlock(ubox.lock); 847 return 0; 848 } 849 850 uid = argv[0]; 851 if(*uid == ':') 852 uid++; 853 if((u = _userByUid(ubox.box, uid)) != nil){ 854 vtSetError("uname: uid '%s' already exists", 855 u->uid); 856 vtUnlock(ubox.lock); 857 return 0; 858 } 859 860 u = userAlloc(uid, uname); 861 uboxAddUser(ubox.box, u); 862 if(argv[0][0] != ':'){ 863 // should have an option for the mode and gid 864 p = smprint(createfmt, uname, uname, uname); 865 r = cliExec(p); 866 vtMemFree(p); 867 if(r == 0){ 868 vtUnlock(ubox.lock); 869 return 0; 870 } 871 } 872 } 873 argv++; 874 } 875 876 if(usersFileWrite(ubox.box) == 0){ 877 vtUnlock(ubox.lock); 878 return 0; 879 } 880 if(dflag) 881 uboxDump(ubox.box); 882 vtUnlock(ubox.lock); 883 884 return 1; 885 } 886 887 static int 888 cmdUsers(int argc, char* argv[]) 889 { 890 Ubox *box; 891 int dflag, r, wflag; 892 char *file; 893 char *usage = "usage: users [-d | -r file] [-w]"; 894 895 dflag = wflag = 0; 896 file = nil; 897 898 ARGBEGIN{ 899 default: 900 return cliError(usage); 901 case 'd': 902 dflag = 1; 903 break; 904 case 'r': 905 file = ARGF(); 906 if(file == nil) 907 return cliError(usage); 908 break; 909 case 'w': 910 wflag = 1; 911 break; 912 }ARGEND 913 914 if(argc) 915 return cliError(usage); 916 917 if(dflag && file) 918 return cliError("cannot use -d and -r together"); 919 920 if(dflag) 921 uboxInit(usersDefault, sizeof(usersDefault)); 922 else if(file){ 923 if(usersFileRead(file) == 0) 924 return 0; 925 } 926 927 vtRLock(ubox.lock); 928 box = ubox.box; 929 consPrint("\tnuser %d len %d\n", box->nuser, box->len); 930 931 r = 1; 932 if(wflag) 933 r = usersFileWrite(box); 934 vtRUnlock(ubox.lock); 935 return r; 936 } 937 938 int 939 usersInit(void) 940 { 941 fmtinstall('U', userFmt); 942 943 ubox.lock = vtLockAlloc(); 944 uboxInit(usersDefault, sizeof(usersDefault)); 945 946 cliAddCmd("users", cmdUsers); 947 cliAddCmd("uname", cmdUname); 948 949 return 1; 950 } 951