9user.c (17154B)
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 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 }; 23 24 #pragma varargck type "U" User* 25 26 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 }; 35 36 static struct { 37 RWLock 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::glenda\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 werrstr("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 rlock(&ubox.lock); 97 if((u = _userByUid(ubox.box, uid)) == nil){ 98 runlock(&ubox.lock); 99 return nil; 100 } 101 uname = vtstrdup(u->uname); 102 runlock(&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 werrstr("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 rlock(&ubox.lock); 129 if((u = _userByUname(ubox.box, uname)) == nil){ 130 runlock(&ubox.lock); 131 return nil; 132 } 133 uid = vtstrdup(u->uid); 134 runlock(&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 rlock(&ubox.lock); 186 ret = _groupMember(ubox.box, "write", uname, 1); 187 runlock(&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 werrstr("uname: '%s' always in own group", member); 206 else 207 werrstr("uname: '%s' not in group '%s'", 208 member, g->uname); 209 return 0; 210 } 211 212 vtfree(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 vtfree(g->group); 221 g->group = nil; 222 break; 223 default: 224 for(; i < g->ngroup; i++) 225 g->group[i] = g->group[i+1]; 226 g->group[i] = nil; /* prevent accidents */ 227 g->group = vtrealloc(g->group, g->ngroup * sizeof(char*)); 228 break; 229 } 230 231 return 1; 232 } 233 234 static int 235 _groupAddMember(Ubox* box, User* g, char* member) 236 { 237 User *u; 238 239 if((u = _userByUname(box, member)) == nil) 240 return 0; 241 if(_groupMember(box, g->uid, u->uname, 0)){ 242 if(strcmp(g->uname, member) == 0) 243 werrstr("uname: '%s' always in own group", member); 244 else 245 werrstr("uname: '%s' already in group '%s'", 246 member, g->uname); 247 return 0; 248 } 249 250 g->group = vtrealloc(g->group, (g->ngroup+1)*sizeof(char*)); 251 g->group[g->ngroup] = vtstrdup(member); 252 box->len += strlen(member); 253 g->ngroup++; 254 if(g->ngroup > 1) 255 box->len++; 256 257 return 1; 258 } 259 260 int 261 groupMember(char* group, char* member) 262 { 263 int r; 264 265 if(group == nil) 266 return 0; 267 268 rlock(&ubox.lock); 269 r = _groupMember(ubox.box, group, member, 0); 270 runlock(&ubox.lock); 271 272 return r; 273 } 274 275 int 276 groupLeader(char* group, char* member) 277 { 278 int r; 279 User *g; 280 281 /* 282 * Is 'member' the leader of 'group'? 283 * Note that 'group' is a 'uid' and not a 'uname'. 284 * Uname 'none' cannot be a group leader. 285 */ 286 if(strcmp(member, unamenone) == 0 || group == nil) 287 return 0; 288 289 rlock(&ubox.lock); 290 if((g = _userByUid(ubox.box, group)) == nil){ 291 runlock(&ubox.lock); 292 return 0; 293 } 294 if(g->leader != nil){ 295 if(strcmp(g->leader, member) == 0){ 296 runlock(&ubox.lock); 297 return 1; 298 } 299 r = 0; 300 } 301 else 302 r = _groupMember(ubox.box, group, member, 0); 303 runlock(&ubox.lock); 304 305 return r; 306 } 307 308 static void 309 userFree(User* u) 310 { 311 int i; 312 313 vtfree(u->uid); 314 vtfree(u->uname); 315 if(u->leader != nil) 316 vtfree(u->leader); 317 if(u->ngroup){ 318 for(i = 0; i < u->ngroup; i++) 319 vtfree(u->group[i]); 320 vtfree(u->group); 321 } 322 vtfree(u); 323 } 324 325 static User* 326 userAlloc(char* uid, char* uname) 327 { 328 User *u; 329 330 u = vtmallocz(sizeof(User)); 331 u->uid = vtstrdup(uid); 332 u->uname = vtstrdup(uname); 333 334 return u; 335 } 336 337 int 338 validUserName(char* name) 339 { 340 Rune *r; 341 #ifdef PLAN9PORT 342 static Rune invalid[] = {'#', ':', ',', '(', ')', '\0'}; 343 #else 344 static Rune invalid[] = L"#:,()"; 345 #endif 346 347 for(r = invalid; *r != '\0'; r++){ 348 if(utfrune(name, *r)) 349 return 0; 350 } 351 return 1; 352 } 353 354 static int 355 userFmt(Fmt* fmt) 356 { 357 User *u; 358 int i, r; 359 360 u = va_arg(fmt->args, User*); 361 362 r = fmtprint(fmt, "%s:%s:", u->uid, u->uname); 363 if(u->leader != nil) 364 r += fmtprint(fmt, u->leader); 365 r += fmtprint(fmt, ":"); 366 if(u->ngroup){ 367 r += fmtprint(fmt, u->group[0]); 368 for(i = 1; i < u->ngroup; i++) 369 r += fmtprint(fmt, ",%s", u->group[i]); 370 } 371 372 return r; 373 } 374 375 static int 376 usersFileWrite(Ubox* box) 377 { 378 Fs *fs; 379 User *u; 380 int i, r; 381 Fsys *fsys; 382 char *p, *q, *s; 383 File *dir, *file; 384 385 if((fsys = fsysGet("main")) == nil) 386 return 0; 387 fsysFsRlock(fsys); 388 fs = fsysGetFs(fsys); 389 390 /* 391 * BUG: 392 * the owner/group/permissions need to be thought out. 393 */ 394 r = 0; 395 if((dir = fileOpen(fs, "/active")) == nil) 396 goto tidy0; 397 if((file = fileWalk(dir, uidadm)) == nil) 398 file = fileCreate(dir, uidadm, ModeDir|0775, uidadm); 399 fileDecRef(dir); 400 if(file == nil) 401 goto tidy; 402 dir = file; 403 if((file = fileWalk(dir, "users")) == nil) 404 file = fileCreate(dir, "users", 0664, uidadm); 405 fileDecRef(dir); 406 if(file == nil) 407 goto tidy; 408 if(!fileTruncate(file, uidadm)) 409 goto tidy; 410 411 p = s = vtmalloc(box->len+1); 412 q = p + box->len+1; 413 for(u = box->head; u != nil; u = u->next){ 414 p += snprint(p, q-p, "%s:%s:", u->uid, u->uname); 415 if(u->leader != nil) 416 p+= snprint(p, q-p, u->leader); 417 p += snprint(p, q-p, ":"); 418 if(u->ngroup){ 419 p += snprint(p, q-p, u->group[0]); 420 for(i = 1; i < u->ngroup; i++) 421 p += snprint(p, q-p, ",%s", u->group[i]); 422 } 423 p += snprint(p, q-p, "\n"); 424 } 425 r = fileWrite(file, s, box->len, 0, uidadm); 426 vtfree(s); 427 428 tidy: 429 if(file != nil) 430 fileDecRef(file); 431 tidy0: 432 fsysFsRUnlock(fsys); 433 fsysPut(fsys); 434 435 return r; 436 } 437 438 static void 439 uboxRemUser(Ubox* box, User *u) 440 { 441 User **h, *up; 442 443 h = &box->ihash[userHash(u->uid)]; 444 for(up = *h; up != nil && up != u; up = up->ihash) 445 h = &up->ihash; 446 assert(up == u); 447 *h = up->ihash; 448 box->len -= strlen(u->uid); 449 450 h = &box->nhash[userHash(u->uname)]; 451 for(up = *h; up != nil && up != u; up = up->nhash) 452 h = &up->nhash; 453 assert(up == u); 454 *h = up->nhash; 455 box->len -= strlen(u->uname); 456 457 h = &box->head; 458 for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next) 459 h = &up->next; 460 assert(up == u); 461 *h = u->next; 462 u->next = nil; 463 464 box->len -= 4; 465 box->nuser--; 466 } 467 468 static void 469 uboxAddUser(Ubox* box, User* u) 470 { 471 User **h, *up; 472 473 h = &box->ihash[userHash(u->uid)]; 474 u->ihash = *h; 475 *h = u; 476 box->len += strlen(u->uid); 477 478 h = &box->nhash[userHash(u->uname)]; 479 u->nhash = *h; 480 *h = u; 481 box->len += strlen(u->uname); 482 483 h = &box->head; 484 for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next) 485 h = &up->next; 486 u->next = *h; 487 *h = u; 488 489 box->len += 4; 490 box->nuser++; 491 } 492 493 static void 494 uboxDump(Ubox* box) 495 { 496 User* u; 497 498 consPrint("nuser %d len = %d\n", box->nuser, box->len); 499 500 for(u = box->head; u != nil; u = u->next) 501 consPrint("%U\n", u); 502 } 503 504 static void 505 uboxFree(Ubox* box) 506 { 507 User *next, *u; 508 509 for(u = box->head; u != nil; u = next){ 510 next = u->next; 511 userFree(u); 512 } 513 vtfree(box); 514 } 515 516 static int 517 uboxInit(char* users, int len) 518 { 519 User *g, *u; 520 Ubox *box, *obox; 521 int blank, comment, i, nline, nuser; 522 char *buf, *f[5], **line, *p, *q, *s; 523 524 /* 525 * Strip out whitespace and comments. 526 * Note that comments are pointless, they disappear 527 * when the server writes the database back out. 528 */ 529 blank = 1; 530 comment = nline = 0; 531 532 s = p = buf = vtmalloc(len+1); 533 for(q = users; *q != '\0'; q++){ 534 if(*q == '\r' || *q == '\t' || *q == ' ') 535 continue; 536 if(*q == '\n'){ 537 if(!blank){ 538 if(p != s){ 539 *p++ = '\n'; 540 nline++; 541 s = p; 542 } 543 blank = 1; 544 } 545 comment = 0; 546 continue; 547 } 548 if(*q == '#') 549 comment = 1; 550 blank = 0; 551 if(!comment) 552 *p++ = *q; 553 } 554 *p = '\0'; 555 556 line = vtmallocz((nline+2)*sizeof(char*)); 557 if((i = gettokens(buf, line, nline+2, "\n")) != nline){ 558 fprint(2, "nline %d (%d) botch\n", nline, i); 559 vtfree(line); 560 vtfree(buf); 561 return 0; 562 } 563 564 /* 565 * Everything is updated in a local Ubox until verified. 566 */ 567 box = vtmallocz(sizeof(Ubox)); 568 569 /* 570 * First pass - check format, check for duplicates 571 * and enter in hash buckets. 572 */ 573 nuser = 0; 574 for(i = 0; i < nline; 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 vtfree(s); 579 continue; 580 } 581 if(*f[0] == '\0' || *f[1] == '\0'){ 582 fprint(2, "bad line '%s'\n", line[i]); 583 vtfree(s); 584 continue; 585 } 586 if(!validUserName(f[0])){ 587 fprint(2, "invalid uid '%s'\n", f[0]); 588 vtfree(s); 589 continue; 590 } 591 if(_userByUid(box, f[0]) != nil){ 592 fprint(2, "duplicate uid '%s'\n", f[0]); 593 vtfree(s); 594 continue; 595 } 596 if(!validUserName(f[1])){ 597 fprint(2, "invalid uname '%s'\n", f[0]); 598 vtfree(s); 599 continue; 600 } 601 if(_userByUname(box, f[1]) != nil){ 602 fprint(2, "duplicate uname '%s'\n", f[1]); 603 vtfree(s); 604 continue; 605 } 606 607 u = userAlloc(f[0], f[1]); 608 uboxAddUser(box, u); 609 line[nuser] = line[i]; 610 nuser++; 611 612 vtfree(s); 613 } 614 assert(box->nuser == nuser); 615 616 /* 617 * Second pass - fill in leader and group information. 618 */ 619 for(i = 0; i < nuser; i++){ 620 s = vtstrdup(line[i]); 621 getfields(s, f, nelem(f), 0, ":"); 622 623 assert(g = _userByUname(box, f[1])); 624 if(*f[2] != '\0'){ 625 if((u = _userByUname(box, f[2])) == nil) 626 g->leader = vtstrdup(g->uname); 627 else 628 g->leader = vtstrdup(u->uname); 629 box->len += strlen(g->leader); 630 } 631 for(p = f[3]; p != nil; p = q){ 632 if((q = utfrune(p, L',')) != nil) 633 *q++ = '\0'; 634 if(!_groupAddMember(box, g, p)){ 635 // print/log error here 636 } 637 } 638 639 vtfree(s); 640 } 641 642 vtfree(line); 643 vtfree(buf); 644 645 for(i = 0; usersMandatory[i] != nil; i++){ 646 if((u = _userByUid(box, usersMandatory[i])) == nil){ 647 werrstr("user '%s' is mandatory", usersMandatory[i]); 648 uboxFree(box); 649 return 0; 650 } 651 if(strcmp(u->uid, u->uname) != 0){ 652 werrstr("uid/uname for user '%s' must match", 653 usersMandatory[i]); 654 uboxFree(box); 655 return 0; 656 } 657 } 658 659 wlock(&ubox.lock); 660 obox = ubox.box; 661 ubox.box = box; 662 wunlock(&ubox.lock); 663 664 if(obox != nil) 665 uboxFree(obox); 666 667 return 1; 668 } 669 670 int 671 usersFileRead(char* path) 672 { 673 char *p; 674 File *file; 675 Fsys *fsys; 676 int len, r; 677 uvlong size; 678 679 if((fsys = fsysGet("main")) == nil) 680 return 0; 681 fsysFsRlock(fsys); 682 683 if(path == nil) 684 path = "/active/adm/users"; 685 686 r = 0; 687 if((file = fileOpen(fsysGetFs(fsys), path)) != nil){ 688 if(fileGetSize(file, &size)){ 689 len = size; 690 p = vtmalloc(size+1); 691 if(fileRead(file, p, len, 0) == len){ 692 p[len] = '\0'; 693 r = uboxInit(p, len); 694 } 695 } 696 fileDecRef(file); 697 } 698 699 fsysFsRUnlock(fsys); 700 fsysPut(fsys); 701 702 return r; 703 } 704 705 static int 706 cmdUname(int argc, char* argv[]) 707 { 708 User *u, *up; 709 int d, dflag, i, r; 710 char *p, *uid, *uname; 711 char *createfmt = "fsys main create /active/usr/%s %s %s d775"; 712 char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]"; 713 714 dflag = 0; 715 716 ARGBEGIN{ 717 default: 718 return cliError(usage); 719 case 'd': 720 dflag = 1; 721 break; 722 }ARGEND 723 724 if(argc < 1){ 725 if(!dflag) 726 return cliError(usage); 727 rlock(&ubox.lock); 728 uboxDump(ubox.box); 729 runlock(&ubox.lock); 730 return 1; 731 } 732 733 uname = argv[0]; 734 argc--; argv++; 735 736 if(argc == 0){ 737 rlock(&ubox.lock); 738 if((u = _userByUname(ubox.box, uname)) == nil){ 739 runlock(&ubox.lock); 740 return 0; 741 } 742 consPrint("\t%U\n", u); 743 runlock(&ubox.lock); 744 return 1; 745 } 746 747 wlock(&ubox.lock); 748 u = _userByUname(ubox.box, uname); 749 while(argc--){ 750 if(argv[0][0] == '%'){ 751 if(u == nil){ 752 wunlock(&ubox.lock); 753 return 0; 754 } 755 p = &argv[0][1]; 756 if((up = _userByUname(ubox.box, p)) != nil){ 757 werrstr("uname: uname '%s' already exists", 758 up->uname); 759 wunlock(&ubox.lock); 760 return 0; 761 } 762 for(i = 0; usersMandatory[i] != nil; i++){ 763 if(strcmp(usersMandatory[i], uname) != 0) 764 continue; 765 werrstr("uname: uname '%s' is mandatory", 766 uname); 767 wunlock(&ubox.lock); 768 return 0; 769 } 770 771 d = strlen(p) - strlen(u->uname); 772 for(up = ubox.box->head; up != nil; up = up->next){ 773 if(up->leader != nil){ 774 if(strcmp(up->leader, u->uname) == 0){ 775 vtfree(up->leader); 776 up->leader = vtstrdup(p); 777 ubox.box->len += d; 778 } 779 } 780 for(i = 0; i < up->ngroup; i++){ 781 if(strcmp(up->group[i], u->uname) != 0) 782 continue; 783 vtfree(up->group[i]); 784 up->group[i] = vtstrdup(p); 785 ubox.box->len += d; 786 break; 787 } 788 } 789 790 uboxRemUser(ubox.box, u); 791 vtfree(u->uname); 792 u->uname = vtstrdup(p); 793 uboxAddUser(ubox.box, u); 794 } 795 else if(argv[0][0] == '='){ 796 if(u == nil){ 797 wunlock(&ubox.lock); 798 return 0; 799 } 800 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 801 if(argv[0][1] != '\0'){ 802 wunlock(&ubox.lock); 803 return 0; 804 } 805 } 806 if(u->leader != nil){ 807 ubox.box->len -= strlen(u->leader); 808 vtfree(u->leader); 809 u->leader = nil; 810 } 811 if(up != nil){ 812 u->leader = vtstrdup(up->uname); 813 ubox.box->len += strlen(u->leader); 814 } 815 } 816 else if(argv[0][0] == '+'){ 817 if(u == nil){ 818 wunlock(&ubox.lock); 819 return 0; 820 } 821 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 822 wunlock(&ubox.lock); 823 return 0; 824 } 825 if(!_groupAddMember(ubox.box, u, up->uname)){ 826 wunlock(&ubox.lock); 827 return 0; 828 } 829 } 830 else if(argv[0][0] == '-'){ 831 if(u == nil){ 832 wunlock(&ubox.lock); 833 return 0; 834 } 835 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ 836 wunlock(&ubox.lock); 837 return 0; 838 } 839 if(!_groupRemMember(ubox.box, u, up->uname)){ 840 wunlock(&ubox.lock); 841 return 0; 842 } 843 } 844 else{ 845 if(u != nil){ 846 werrstr("uname: uname '%s' already exists", 847 u->uname); 848 wunlock(&ubox.lock); 849 return 0; 850 } 851 852 uid = argv[0]; 853 if(*uid == ':') 854 uid++; 855 if((u = _userByUid(ubox.box, uid)) != nil){ 856 werrstr("uname: uid '%s' already exists", 857 u->uid); 858 wunlock(&ubox.lock); 859 return 0; 860 } 861 862 u = userAlloc(uid, uname); 863 uboxAddUser(ubox.box, u); 864 if(argv[0][0] != ':'){ 865 // should have an option for the mode and gid 866 p = smprint(createfmt, uname, uname, uname); 867 r = cliExec(p); 868 vtfree(p); 869 if(r == 0){ 870 wunlock(&ubox.lock); 871 return 0; 872 } 873 } 874 } 875 argv++; 876 } 877 878 if(usersFileWrite(ubox.box) == 0){ 879 wunlock(&ubox.lock); 880 return 0; 881 } 882 if(dflag) 883 uboxDump(ubox.box); 884 wunlock(&ubox.lock); 885 886 return 1; 887 } 888 889 static int 890 cmdUsers(int argc, char* argv[]) 891 { 892 Ubox *box; 893 int dflag, r, wflag; 894 char *file; 895 char *usage = "usage: users [-d | -r file] [-w]"; 896 897 dflag = wflag = 0; 898 file = nil; 899 900 ARGBEGIN{ 901 default: 902 return cliError(usage); 903 case 'd': 904 dflag = 1; 905 break; 906 case 'r': 907 file = ARGF(); 908 if(file == nil) 909 return cliError(usage); 910 break; 911 case 'w': 912 wflag = 1; 913 break; 914 }ARGEND 915 916 if(argc) 917 return cliError(usage); 918 919 if(dflag && file) 920 return cliError("cannot use -d and -r together"); 921 922 if(dflag) 923 uboxInit(usersDefault, sizeof(usersDefault)); 924 else if(file){ 925 if(usersFileRead(file) == 0) 926 return 0; 927 } 928 929 rlock(&ubox.lock); 930 box = ubox.box; 931 consPrint("\tnuser %d len %d\n", box->nuser, box->len); 932 933 r = 1; 934 if(wflag) 935 r = usersFileWrite(box); 936 runlock(&ubox.lock); 937 return r; 938 } 939 940 int 941 usersInit(void) 942 { 943 fmtinstall('U', userFmt); 944 945 uboxInit(usersDefault, sizeof(usersDefault)); 946 947 cliAddCmd("users", cmdUsers); 948 cliAddCmd("uname", cmdUname); 949 950 return 1; 951 }