123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948 |
- #include "stdinc.h"
- #include "9.h"
- enum {
- NUserHash = 1009,
- };
- typedef struct Ubox Ubox;
- typedef struct User User;
- typedef struct User {
- char* uid;
- char* uname;
- char* leader;
- char** group;
- int ngroup;
- User* next; /* */
- User* ihash; /* lookup by .uid */
- User* nhash; /* lookup by .uname */
- } User;
- #pragma varargck type "U" User*
- typedef struct Ubox {
- User* head;
- User* tail;
- int nuser;
- int len;
- User* ihash[NUserHash]; /* lookup by .uid */
- User* nhash[NUserHash]; /* lookup by .uname */
- } Ubox;
- static struct {
- VtLock* lock;
- Ubox* box;
- } ubox;
- static char usersDefault[] = {
- "adm:adm:adm:sys\n"
- "none:none::\n"
- "noworld:noworld::\n"
- "sys:sys::glenda\n"
- "glenda:glenda:glenda:\n"
- };
- static char* usersMandatory[] = {
- "adm",
- "none",
- "noworld",
- "sys",
- nil,
- };
- char* uidadm = "adm";
- char* unamenone = "none";
- char* uidnoworld = "noworld";
- static u32int
- userHash(char* s)
- {
- uchar *p;
- u32int hash;
- hash = 0;
- for(p = (uchar*)s; *p != '\0'; p++)
- hash = hash*7 + *p;
- return hash % NUserHash;
- }
- static User*
- _userByUid(Ubox* box, char* uid)
- {
- User *u;
- if(box != nil){
- for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
- if(strcmp(u->uid, uid) == 0)
- return u;
- }
- }
- vtSetError("uname: uid '%s' not found", uid);
- return nil;
- }
- char*
- unameByUid(char* uid)
- {
- User *u;
- char *uname;
- vtRLock(ubox.lock);
- if((u = _userByUid(ubox.box, uid)) == nil){
- vtRUnlock(ubox.lock);
- return nil;
- }
- uname = vtStrDup(u->uname);
- vtRUnlock(ubox.lock);
- return uname;
- }
- static User*
- _userByUname(Ubox* box, char* uname)
- {
- User *u;
- if(box != nil){
- for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
- if(strcmp(u->uname, uname) == 0)
- return u;
- }
- }
- vtSetError("uname: uname '%s' not found", uname);
- return nil;
- }
- char*
- uidByUname(char* uname)
- {
- User *u;
- char *uid;
- vtRLock(ubox.lock);
- if((u = _userByUname(ubox.box, uname)) == nil){
- vtRUnlock(ubox.lock);
- return nil;
- }
- uid = vtStrDup(u->uid);
- vtRUnlock(ubox.lock);
- return uid;
- }
- static int
- _groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
- {
- int i;
- User *g, *m;
- /*
- * Is 'member' a member of 'group'?
- * Note that 'group' is a 'uid' and not a 'uname'.
- * A 'member' is automatically in their own group.
- */
- if((g = _userByUid(box, group)) == nil)
- return whenNoGroup;
- if((m = _userByUname(box, member)) == nil)
- return 0;
- if(m == g)
- return 1;
- for(i = 0; i < g->ngroup; i++){
- if(strcmp(g->group[i], member) == 0)
- return 1;
- }
- return 0;
- }
- int
- groupWriteMember(char* uname)
- {
- int ret;
- /*
- * If there is a ``write'' group, then only its members can write
- * to the file system, no matter what the permission bits say.
- *
- * To users not in the ``write'' group, the file system appears
- * read only. This is used to serve sources.cs.bell-labs.com
- * to the world.
- *
- * Note that if there is no ``write'' group, then this routine
- * makes it look like everyone is a member -- the opposite
- * of what groupMember does.
- *
- * We use this for sources.cs.bell-labs.com.
- * If this slows things down too much on systems that don't
- * use this functionality, we could cache the write group lookup.
- */
- vtRLock(ubox.lock);
- ret = _groupMember(ubox.box, "write", uname, 1);
- vtRUnlock(ubox.lock);
- return ret;
- }
- static int
- _groupRemMember(Ubox* box, User* g, char* member)
- {
- int i;
- if(_userByUname(box, member) == nil)
- return 0;
- for(i = 0; i < g->ngroup; i++){
- if(strcmp(g->group[i], member) == 0)
- break;
- }
- if(i >= g->ngroup){
- if(strcmp(g->uname, member) == 0)
- vtSetError("uname: '%s' always in own group", member);
- else
- vtSetError("uname: '%s' not in group '%s'",
- member, g->uname);
- return 0;
- }
- vtMemFree(g->group[i]);
- box->len -= strlen(member);
- if(g->ngroup > 1)
- box->len--;
- g->ngroup--;
- switch(g->ngroup){
- case 0:
- vtMemFree(g->group);
- g->group = nil;
- break;
- default:
- for(; i < g->ngroup; i++)
- g->group[i] = g->group[i+1];
- g->group[i] = nil; /* prevent accidents */
- g->group = vtMemRealloc(g->group, g->ngroup * sizeof(char*));
- break;
- }
- return 1;
- }
- static int
- _groupAddMember(Ubox* box, User* g, char* member)
- {
- User *u;
- if((u = _userByUname(box, member)) == nil)
- return 0;
- if(_groupMember(box, g->uid, u->uname, 0)){
- if(strcmp(g->uname, member) == 0)
- vtSetError("uname: '%s' always in own group", member);
- else
- vtSetError("uname: '%s' already in group '%s'",
- member, g->uname);
- return 0;
- }
- g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*));
- g->group[g->ngroup] = vtStrDup(member);
- box->len += strlen(member);
- g->ngroup++;
- if(g->ngroup > 1)
- box->len++;
- return 1;
- }
- int
- groupMember(char* group, char* member)
- {
- int r;
- if(group == nil)
- return 0;
- vtRLock(ubox.lock);
- r = _groupMember(ubox.box, group, member, 0);
- vtRUnlock(ubox.lock);
- return r;
- }
- int
- groupLeader(char* group, char* member)
- {
- int r;
- User *g;
- /*
- * Is 'member' the leader of 'group'?
- * Note that 'group' is a 'uid' and not a 'uname'.
- * Uname 'none' cannot be a group leader.
- */
- if(strcmp(member, unamenone) == 0 || group == nil)
- return 0;
- vtRLock(ubox.lock);
- if((g = _userByUid(ubox.box, group)) == nil){
- vtRUnlock(ubox.lock);
- return 0;
- }
- if(g->leader != nil){
- if(strcmp(g->leader, member) == 0){
- vtRUnlock(ubox.lock);
- return 1;
- }
- r = 0;
- }
- else
- r = _groupMember(ubox.box, group, member, 0);
- vtRUnlock(ubox.lock);
- return r;
- }
- static void
- userFree(User* u)
- {
- int i;
- vtMemFree(u->uid);
- vtMemFree(u->uname);
- if(u->leader != nil)
- vtMemFree(u->leader);
- if(u->ngroup){
- for(i = 0; i < u->ngroup; i++)
- vtMemFree(u->group[i]);
- vtMemFree(u->group);
- }
- vtMemFree(u);
- }
- static User*
- userAlloc(char* uid, char* uname)
- {
- User *u;
- u = vtMemAllocZ(sizeof(User));
- u->uid = vtStrDup(uid);
- u->uname = vtStrDup(uname);
- return u;
- }
- int
- validUserName(char* name)
- {
- Rune *r;
- static Rune invalid[] = L"#:,()";
- for(r = invalid; *r != '\0'; r++){
- if(utfrune(name, *r))
- return 0;
- }
- return 1;
- }
- static int
- userFmt(Fmt* fmt)
- {
- User *u;
- int i, r;
- u = va_arg(fmt->args, User*);
- r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
- if(u->leader != nil)
- r += fmtprint(fmt, u->leader);
- r += fmtprint(fmt, ":");
- if(u->ngroup){
- r += fmtprint(fmt, u->group[0]);
- for(i = 1; i < u->ngroup; i++)
- r += fmtprint(fmt, ",%s", u->group[i]);
- }
- return r;
- }
- static int
- usersFileWrite(Ubox* box)
- {
- Fs *fs;
- User *u;
- int i, r;
- Fsys *fsys;
- char *p, *q, *s;
- File *dir, *file;
- if((fsys = fsysGet("main")) == nil)
- return 0;
- fsysFsRlock(fsys);
- fs = fsysGetFs(fsys);
- /*
- * BUG:
- * the owner/group/permissions need to be thought out.
- */
- r = 0;
- if((dir = fileOpen(fs, "/active")) == nil)
- goto tidy0;
- if((file = fileWalk(dir, uidadm)) == nil)
- file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
- fileDecRef(dir);
- if(file == nil)
- goto tidy;
- dir = file;
- if((file = fileWalk(dir, "users")) == nil)
- file = fileCreate(dir, "users", 0664, uidadm);
- fileDecRef(dir);
- if(file == nil)
- goto tidy;
- if(!fileTruncate(file, uidadm))
- goto tidy;
- p = s = vtMemAlloc(box->len+1);
- q = p + box->len+1;
- for(u = box->head; u != nil; u = u->next){
- p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
- if(u->leader != nil)
- p+= snprint(p, q-p, u->leader);
- p += snprint(p, q-p, ":");
- if(u->ngroup){
- p += snprint(p, q-p, u->group[0]);
- for(i = 1; i < u->ngroup; i++)
- p += snprint(p, q-p, ",%s", u->group[i]);
- }
- p += snprint(p, q-p, "\n");
- }
- r = fileWrite(file, s, box->len, 0, uidadm);
- vtMemFree(s);
- tidy:
- if(file != nil)
- fileDecRef(file);
- tidy0:
- fsysFsRUnlock(fsys);
- fsysPut(fsys);
- return r;
- }
- static void
- uboxRemUser(Ubox* box, User *u)
- {
- User **h, *up;
- h = &box->ihash[userHash(u->uid)];
- for(up = *h; up != nil && up != u; up = up->ihash)
- h = &up->ihash;
- assert(up == u);
- *h = up->ihash;
- box->len -= strlen(u->uid);
- h = &box->nhash[userHash(u->uname)];
- for(up = *h; up != nil && up != u; up = up->nhash)
- h = &up->nhash;
- assert(up == u);
- *h = up->nhash;
- box->len -= strlen(u->uname);
- h = &box->head;
- for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
- h = &up->next;
- assert(up == u);
- *h = u->next;
- u->next = nil;
- box->len -= 4;
- box->nuser--;
- }
- static void
- uboxAddUser(Ubox* box, User* u)
- {
- User **h, *up;
- h = &box->ihash[userHash(u->uid)];
- u->ihash = *h;
- *h = u;
- box->len += strlen(u->uid);
- h = &box->nhash[userHash(u->uname)];
- u->nhash = *h;
- *h = u;
- box->len += strlen(u->uname);
- h = &box->head;
- for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
- h = &up->next;
- u->next = *h;
- *h = u;
- box->len += 4;
- box->nuser++;
- }
- static void
- uboxDump(Ubox* box)
- {
- User* u;
- consPrint("nuser %d len = %d\n", box->nuser, box->len);
- for(u = box->head; u != nil; u = u->next)
- consPrint("%U\n", u);
- }
- static void
- uboxFree(Ubox* box)
- {
- User *next, *u;
- for(u = box->head; u != nil; u = next){
- next = u->next;
- userFree(u);
- }
- vtMemFree(box);
- }
- static int
- uboxInit(char* users, int len)
- {
- User *g, *u;
- Ubox *box, *obox;
- int blank, comment, i, nline, nuser;
- char *buf, *f[5], **line, *p, *q, *s;
- /*
- * Strip out whitespace and comments.
- * Note that comments are pointless, they disappear
- * when the server writes the database back out.
- */
- blank = 1;
- comment = nline = 0;
- s = p = buf = vtMemAlloc(len+1);
- for(q = users; *q != '\0'; q++){
- if(*q == '\r' || *q == '\t' || *q == ' ')
- continue;
- if(*q == '\n'){
- if(!blank){
- if(p != s){
- *p++ = '\n';
- nline++;
- s = p;
- }
- blank = 1;
- }
- comment = 0;
- continue;
- }
- if(*q == '#')
- comment = 1;
- blank = 0;
- if(!comment)
- *p++ = *q;
- }
- *p = '\0';
- line = vtMemAllocZ((nline+2)*sizeof(char*));
- if((i = gettokens(buf, line, nline+2, "\n")) != nline){
- fprint(2, "nline %d (%d) botch\n", nline, i);
- vtMemFree(line);
- vtMemFree(buf);
- return 0;
- }
- /*
- * Everything is updated in a local Ubox until verified.
- */
- box = vtMemAllocZ(sizeof(Ubox));
- /*
- * First pass - check format, check for duplicates
- * and enter in hash buckets.
- */
- nuser = 0;
- for(i = 0; i < nline; i++){
- s = vtStrDup(line[i]);
- if(getfields(s, f, nelem(f), 0, ":") != 4){
- fprint(2, "bad line '%s'\n", line[i]);
- vtMemFree(s);
- continue;
- }
- if(*f[0] == '\0' || *f[1] == '\0'){
- fprint(2, "bad line '%s'\n", line[i]);
- vtMemFree(s);
- continue;
- }
- if(!validUserName(f[0])){
- fprint(2, "invalid uid '%s'\n", f[0]);
- vtMemFree(s);
- continue;
- }
- if(_userByUid(box, f[0]) != nil){
- fprint(2, "duplicate uid '%s'\n", f[0]);
- vtMemFree(s);
- continue;
- }
- if(!validUserName(f[1])){
- fprint(2, "invalid uname '%s'\n", f[0]);
- vtMemFree(s);
- continue;
- }
- if(_userByUname(box, f[1]) != nil){
- fprint(2, "duplicate uname '%s'\n", f[1]);
- vtMemFree(s);
- continue;
- }
- u = userAlloc(f[0], f[1]);
- uboxAddUser(box, u);
- line[nuser] = line[i];
- nuser++;
- vtMemFree(s);
- }
- assert(box->nuser == nuser);
- /*
- * Second pass - fill in leader and group information.
- */
- for(i = 0; i < nuser; i++){
- s = vtStrDup(line[i]);
- getfields(s, f, nelem(f), 0, ":");
- assert(g = _userByUname(box, f[1]));
- if(*f[2] != '\0'){
- if((u = _userByUname(box, f[2])) == nil)
- g->leader = vtStrDup(g->uname);
- else
- g->leader = vtStrDup(u->uname);
- box->len += strlen(g->leader);
- }
- for(p = f[3]; p != nil; p = q){
- if((q = utfrune(p, L',')) != nil)
- *q++ = '\0';
- if(!_groupAddMember(box, g, p)){
- // print/log error here
- }
- }
- vtMemFree(s);
- }
- vtMemFree(line);
- vtMemFree(buf);
- for(i = 0; usersMandatory[i] != nil; i++){
- if((u = _userByUid(box, usersMandatory[i])) == nil){
- vtSetError("user '%s' is mandatory", usersMandatory[i]);
- uboxFree(box);
- return 0;
- }
- if(strcmp(u->uid, u->uname) != 0){
- vtSetError("uid/uname for user '%s' must match",
- usersMandatory[i]);
- uboxFree(box);
- return 0;
- }
- }
- vtLock(ubox.lock);
- obox = ubox.box;
- ubox.box = box;
- vtUnlock(ubox.lock);
- if(obox != nil)
- uboxFree(obox);
- return 1;
- }
- int
- usersFileRead(char* path)
- {
- char *p;
- File *file;
- Fsys *fsys;
- int len, r;
- uvlong size;
- if((fsys = fsysGet("main")) == nil)
- return 0;
- fsysFsRlock(fsys);
- if(path == nil)
- path = "/active/adm/users";
- r = 0;
- if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
- if(fileGetSize(file, &size)){
- len = size;
- p = vtMemAlloc(size+1);
- if(fileRead(file, p, len, 0) == len){
- p[len] = '\0';
- r = uboxInit(p, len);
- }
- }
- fileDecRef(file);
- }
- fsysFsRUnlock(fsys);
- fsysPut(fsys);
- return r;
- }
- static int
- cmdUname(int argc, char* argv[])
- {
- User *u, *up;
- int d, dflag, i, r;
- char *p, *uid, *uname;
- char *createfmt = "fsys main create /active/usr/%s %s %s d775";
- char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
- dflag = 0;
- ARGBEGIN{
- default:
- return cliError(usage);
- case 'd':
- dflag = 1;
- break;
- }ARGEND
- if(argc < 1){
- if(!dflag)
- return cliError(usage);
- vtRLock(ubox.lock);
- uboxDump(ubox.box);
- vtRUnlock(ubox.lock);
- return 1;
- }
- uname = argv[0];
- argc--; argv++;
- if(argc == 0){
- vtRLock(ubox.lock);
- if((u = _userByUname(ubox.box, uname)) == nil){
- vtRUnlock(ubox.lock);
- return 0;
- }
- consPrint("\t%U\n", u);
- vtRUnlock(ubox.lock);
- return 1;
- }
- vtLock(ubox.lock);
- u = _userByUname(ubox.box, uname);
- while(argc--){
- if(argv[0][0] == '%'){
- if(u == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- p = &argv[0][1];
- if((up = _userByUname(ubox.box, p)) != nil){
- vtSetError("uname: uname '%s' already exists",
- up->uname);
- vtUnlock(ubox.lock);
- return 0;
- }
- for(i = 0; usersMandatory[i] != nil; i++){
- if(strcmp(usersMandatory[i], uname) != 0)
- continue;
- vtSetError("uname: uname '%s' is mandatory",
- uname);
- vtUnlock(ubox.lock);
- return 0;
- }
- d = strlen(p) - strlen(u->uname);
- for(up = ubox.box->head; up != nil; up = up->next){
- if(up->leader != nil){
- if(strcmp(up->leader, u->uname) == 0){
- vtMemFree(up->leader);
- up->leader = vtStrDup(p);
- ubox.box->len += d;
- }
- }
- for(i = 0; i < up->ngroup; i++){
- if(strcmp(up->group[i], u->uname) != 0)
- continue;
- vtMemFree(up->group[i]);
- up->group[i] = vtStrDup(p);
- ubox.box->len += d;
- break;
- }
- }
- uboxRemUser(ubox.box, u);
- vtMemFree(u->uname);
- u->uname = vtStrDup(p);
- uboxAddUser(ubox.box, u);
- }
- else if(argv[0][0] == '='){
- if(u == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
- if(argv[0][1] != '\0'){
- vtUnlock(ubox.lock);
- return 0;
- }
- }
- if(u->leader != nil){
- ubox.box->len -= strlen(u->leader);
- vtMemFree(u->leader);
- u->leader = nil;
- }
- if(up != nil){
- u->leader = vtStrDup(up->uname);
- ubox.box->len += strlen(u->leader);
- }
- }
- else if(argv[0][0] == '+'){
- if(u == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- if(!_groupAddMember(ubox.box, u, up->uname)){
- vtUnlock(ubox.lock);
- return 0;
- }
- }
- else if(argv[0][0] == '-'){
- if(u == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
- vtUnlock(ubox.lock);
- return 0;
- }
- if(!_groupRemMember(ubox.box, u, up->uname)){
- vtUnlock(ubox.lock);
- return 0;
- }
- }
- else{
- if(u != nil){
- vtSetError("uname: uname '%s' already exists",
- u->uname);
- vtUnlock(ubox.lock);
- return 0;
- }
- uid = argv[0];
- if(*uid == ':')
- uid++;
- if((u = _userByUid(ubox.box, uid)) != nil){
- vtSetError("uname: uid '%s' already exists",
- u->uid);
- vtUnlock(ubox.lock);
- return 0;
- }
- u = userAlloc(uid, uname);
- uboxAddUser(ubox.box, u);
- if(argv[0][0] != ':'){
- // should have an option for the mode and gid
- p = smprint(createfmt, uname, uname, uname);
- r = cliExec(p);
- vtMemFree(p);
- if(r == 0){
- vtUnlock(ubox.lock);
- return 0;
- }
- }
- }
- argv++;
- }
- if(usersFileWrite(ubox.box) == 0){
- vtUnlock(ubox.lock);
- return 0;
- }
- if(dflag)
- uboxDump(ubox.box);
- vtUnlock(ubox.lock);
- return 1;
- }
- static int
- cmdUsers(int argc, char* argv[])
- {
- Ubox *box;
- int dflag, r, wflag;
- char *file;
- char *usage = "usage: users [-d | -r file] [-w]";
- dflag = wflag = 0;
- file = nil;
- ARGBEGIN{
- default:
- return cliError(usage);
- case 'd':
- dflag = 1;
- break;
- case 'r':
- file = ARGF();
- if(file == nil)
- return cliError(usage);
- break;
- case 'w':
- wflag = 1;
- break;
- }ARGEND
- if(argc)
- return cliError(usage);
- if(dflag && file)
- return cliError("cannot use -d and -r together");
- if(dflag)
- uboxInit(usersDefault, sizeof(usersDefault));
- else if(file){
- if(usersFileRead(file) == 0)
- return 0;
- }
- vtRLock(ubox.lock);
- box = ubox.box;
- consPrint("\tnuser %d len %d\n", box->nuser, box->len);
- r = 1;
- if(wflag)
- r = usersFileWrite(box);
- vtRUnlock(ubox.lock);
- return r;
- }
- int
- usersInit(void)
- {
- fmtinstall('U', userFmt);
- ubox.lock = vtLockAlloc();
- uboxInit(usersDefault, sizeof(usersDefault));
- cliAddCmd("users", cmdUsers);
- cliAddCmd("uname", cmdUname);
- return 1;
- }
|