aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/fossil/9user.c
diff options
context:
space:
mode:
authorDavid du Colombier <0intro@gmail.com>2013-09-23 23:00:39 +0200
committerDavid du Colombier <0intro@gmail.com>2013-09-23 23:00:39 +0200
commit6f4d00ee45693290fae042b27536b54f77b96acd (patch)
tree60ad31bf16ed2000661c02345dd2a63851588a5d /src/cmd/fossil/9user.c
parentfea86f063930ea187f1c77e93207ac8d39125520 (diff)
downloadplan9port-6f4d00ee45693290fae042b27536b54f77b96acd.tar.gz
plan9port-6f4d00ee45693290fae042b27536b54f77b96acd.tar.bz2
plan9port-6f4d00ee45693290fae042b27536b54f77b96acd.zip
fossil: import from plan 9
R=rsc https://codereview.appspot.com/7988047
Diffstat (limited to 'src/cmd/fossil/9user.c')
-rw-r--r--src/cmd/fossil/9user.c948
1 files changed, 948 insertions, 0 deletions
diff --git a/src/cmd/fossil/9user.c b/src/cmd/fossil/9user.c
new file mode 100644
index 00000000..98203d0b
--- /dev/null
+++ b/src/cmd/fossil/9user.c
@@ -0,0 +1,948 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+enum {
+ NUserHash = 1009,
+};
+
+typedef struct Ubox Ubox;
+typedef struct User User;
+
+struct User {
+ char* uid;
+ char* uname;
+ char* leader;
+ char** group;
+ int ngroup;
+
+ User* next; /* */
+ User* ihash; /* lookup by .uid */
+ User* nhash; /* lookup by .uname */
+};
+
+#pragma varargck type "U" User*
+
+struct Ubox {
+ User* head;
+ User* tail;
+ int nuser;
+ int len;
+
+ User* ihash[NUserHash]; /* lookup by .uid */
+ User* nhash[NUserHash]; /* lookup by .uname */
+};
+
+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;
+}