diff --git a/README.md b/README.md index 988bcd8..5efce20 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,18 @@ The client is intended to be a specialized [uxn](https://wiki.xxiivv.com/site/ux This is the working structure of the 9p filesystem: -* `/ctl`: Write-only control file for inputing system commands - * `login PW`: Authenticate with `xrxs` -- password is hashed against realm password hash - * `load CART`: Load a cartridge - * `chunk TYPE N`: Load data of type TYPE and chunk number N +* `/ctl`: Read/write control file for inputing system commands. Reading the file shows the status of the last input command: 1 for success, 0 for failure; `logout` is a special case, and the status code will be -1 if it was succesful. the following are valid command syntax: + * `login PW`: Authenticate with `xrxs` -- password is hashed against realm password hash. + * `logout`: Gracefully remove yourself from the users table. + * `load CART`: Load a cartridge. + * `chunk TYPE N`: Load data of type TYPE and chunk number N. * `create REALM`: Create a new realm (start a new game) -- must have a cartridge loaded * `protect PW`: Protect the curent realm with a password if you are the master. * `transfer USER`: Transfer ownership of the realm to another user. - * `enter REALM`: Join an existing realm - * `leave`: Leave the current realm - * `unload`: Unload the cartridge + * `delete REALM`: Delete the realm off the server if you are the master and it is empty. + * `enter REALM`: Join an existing realm. + * `leave`: Leave the current realm. + * `unload`: Unload the cartridge. * `/users`: Read-only; Self and others in the realm are readable from here, one per line. It contains only yourself before joining a realm. Your username on your machine is used as your username in `xrxs` -- if your name is taken, you will get an error on attaching. @@ -37,6 +39,13 @@ This is the working structure of the 9p filesystem: * `/grandom`: Read-only, get a random number from 0 to 99 -- These are doled out on a per-realm basis, and the number stays the same until everyone in the realm has had a chance to read it. If you've already read it this round or aren't in a realm, it will be empty. +## configuration + +`config.h` in the source contains the following configuration macros: + + * `MAX_USERS`: the maximum number of simultaneous users able to attach to the `xrxs` service + * `DATA_DIR`: the path to the root of the cartridge and realm storage; can be absolute or relative to the `xrxs` executable, but must have the trailing `/` + ## realm format Each realm directory on the server should have the following files: diff --git a/cart.c b/cart.c index ad91f93..0c3b76e 100644 --- a/cart.c +++ b/cart.c @@ -1,18 +1,19 @@ #include #include +#include "config.h" #include "util.h" #include "user.h" #include "cart.h" Cart* create_cart(char* name) { - char path[64] = {0}; - char file[64] = {0}; + char path[256] = {0}; + char file[256] = {0}; Cart* cart; Blob* cart_data; - scat(path, "carts/"); + scat(path, DATA_DIR); scat(path, name); - scpy(path, file, 64); + scpy(path, file, 256); ccat(file, '/'); scat(file, name); scat(file, ".rom"); diff --git a/command.h b/command.h index 4860bd1..fbb1d59 100644 --- a/command.h +++ b/command.h @@ -5,6 +5,7 @@ typedef enum { CREATE = 7083959236, PROTECT = 295480618573, TRANSFER = 11311529048177, + DELETE = 7129299147, ENTER = 195024746, LEAVE = 207662601, LOGOUT = 7702552890, diff --git a/config.h b/config.h new file mode 100644 index 0000000..3313838 --- /dev/null +++ b/config.h @@ -0,0 +1,5 @@ +/* Maximum number of users on the server */ +#define MAX_USERS 64 + +/* Path to cart/realm storage (must include trailing slash) */ +#define DATA_DIR "/home/nilix/src/xrxs/carts/" \ No newline at end of file diff --git a/realm.c b/realm.c index d66c269..41a1a07 100644 --- a/realm.c +++ b/realm.c @@ -1,6 +1,7 @@ #include #include #include +#include "config.h" #include "util.h" #include "user.h" #include "cart.h" @@ -13,7 +14,7 @@ Realm* create_realm(UserInfo* table, char* uname, char* name) { char cart[32]; int max = 4; char* n = name; - char path[128] = {0}; + char path[256] = {0}; if (u == nil || u->cart == nil) return 0; @@ -28,7 +29,7 @@ Realm* create_realm(UserInfo* table, char* uname, char* name) { max = atoi(n); } - scat(path, "carts/"); + scat(path, DATA_DIR); scat(path, cart); scat(path, "/realms/"); if (open(path, OREAD) < 0) @@ -55,14 +56,14 @@ Realm* create_realm(UserInfo* table, char* uname, char* name) { Realm* parse_realm(char* cart, char* name) { Realm* self; FILE* f; - char path[128] = {0}; - char file[128] = {0}; + char path[256] = {0}; + char file[256] = {0}; char buf[256] = {0}; - scat(path, "carts/"); + scat(path, DATA_DIR); scat(path, cart); scat(path, "/realms/"); scat(path, name); - scpy(path, file, 128); + scpy(path, file, 256); scat(file, "/realm"); @@ -85,7 +86,7 @@ Realm* parse_realm(char* cart, char* name) { return nil; } - scpy(path, file, 128); + scpy(path, file, 256); scat(file, "/universe"); self->universe = parse_universe(cart, name); return self; @@ -95,7 +96,7 @@ Realm* find_realm(UserInfo* table, char* name) { UserInfo* u = table; int i; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if (slen(u->name) > 0 && u->realm != nil && scmp(u->realm->name, name)) return u->realm; u++; @@ -105,13 +106,13 @@ Realm* find_realm(UserInfo* table, char* name) { void save_realm(char* cart, Realm* self) { FILE* f; - char path[128] = {0}; - char file[128] = {0}; - scat(path, "carts/"); + char path[256] = {0}; + char file[256] = {0}; + scat(path, DATA_DIR); scat(path, cart); scat(path, "/realms/"); scat(path, self->name); - scpy(path, file, 128); + scpy(path, file, 256); scat(file, "/realm"); f = fopen(file, "w"); diff --git a/universe.c b/universe.c index 4ffb1ec..c4fbc05 100644 --- a/universe.c +++ b/universe.c @@ -1,6 +1,7 @@ #include #include #include +#include "config.h" #include "util.h" #include "universe.h" @@ -14,7 +15,7 @@ Universe* create_universe() { } Universe* parse_universe(char* cart, char* realm_name) { - char path[64] = {0}; + char path[256] = {0}; char buf[256] = {0}; char name[16] = {0}; char value[64] = {0}; @@ -22,7 +23,7 @@ Universe* parse_universe(char* cart, char* realm_name) { Atom* a; Universe* self; - scat(path, "carts/"); + scat(path, DATA_DIR); scat(path, cart); scat(path, "/realms/"); scat(path, realm_name); @@ -52,12 +53,12 @@ Universe* parse_universe(char* cart, char* realm_name) { } void save_universe(char* cart, Universe* self, char* realm_name) { - char path[64] = {0}; + char path[256] = {0}; FILE* f; Atom* a; int i; - scat(path, "carts/"); + scat(path, DATA_DIR); scat(path, cart); scat(path, "/realms/"); scat(path, realm_name); diff --git a/universe.h b/universe.h index 2bee2da..6952973 100644 --- a/universe.h +++ b/universe.h @@ -4,7 +4,7 @@ typedef struct Atom Atom; struct Atom { char name[16]; - char value[64]; + char value[MAX_USERS]; Atom* next; }; diff --git a/user.c b/user.c index 15b4616..b4960b5 100644 --- a/user.c +++ b/user.c @@ -1,6 +1,8 @@ #include +#include #include #include +#include "config.h" #include "util.h" #include "cart.h" #include "realm.h" @@ -11,7 +13,7 @@ extern UserInfo users_table[64]; UserInfo* find_user(UserInfo* table, char* uname) { UserInfo* u = table; int i; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if (scmp(uname, u->name)) return u; u++; @@ -19,6 +21,13 @@ UserInfo* find_user(UserInfo* table, char* uname) { return nil; } +int* ctl_code_handle(UserInfo* table, char* uname) { + UserInfo* u = find_user(table, uname); + if (u != nil) + return &(u->ctl_code); + return nil; +} + int login(UserInfo* table, char* uname, char* password) { UserInfo* u = find_user(table, uname); @@ -43,7 +52,7 @@ int logout(UserInfo* table, char* uname) { u->scope = nil; } - u->random = 0; + u->random = -1; if (u->realm != nil) leave_realm(table, uname); @@ -104,8 +113,7 @@ int enter_realm(UserInfo* table, char* uname, char* realm_name) { return 0; } - for (i = 0; i < 64; i++) { - fprintf(stderr, "iterating through users table: slot %d\n", i); + for (i = 0; i < MAX_USERS; i++) { if (table[i].realm != nil && scmp(table[i].realm->name, realm_name)) j++; } @@ -162,4 +170,49 @@ int unload_cart(UserInfo* table, char* uname) { destroy_cart(u->cart); u->cart = nil; return 1; +} + +int delete_realm(UserInfo* table, char* uname, char* realm) { + UserInfo* u = find_user(table, uname); + char path[256] = {0}; + char file[256] = {0}; + Blob* realm_data; + char garbage[32] = {0}; + char master[32] = {0}; + int i; + if (u == nil || u->cart == nil) { + fprintf(stderr, "no user/cart\n"); + return 0; + } + + for (i = 0; i < MAX_USERS; i++) { + if (table[i].realm != nil && scmp(table[i].realm->name, realm)) { + fprintf(stderr, "realm not empty\n"); + return 0; + } + } + + scat(path, DATA_DIR); + scat(path, u->cart->name); + scat(path, "/realms/"); + scat(path, realm); + scpy(path, file, 256); + ccat(file, '/'); + scat(file, "realm"); + + fprintf(stderr, "%s\n", file); + realm_data = read_chars(file); + if (realm_data == nil) + return 0; + + sscanf( + realm_data->data, + "%u %32s %llu", + (uint*)garbage, + master, + (uvlong*)garbage); + if (scmp(master, uname) && rm_dir(path)) + return 1; + else + return 0; } \ No newline at end of file diff --git a/user.h b/user.h index 71ac1cb..2a5d69d 100644 --- a/user.h +++ b/user.h @@ -7,9 +7,11 @@ typedef struct UserInfo { Realm* realm; char* scope; int random; + int ctl_code; } UserInfo; UserInfo* find_user(UserInfo* table, char* uname); +int* ctl_code_handle(UserInfo* table, char* uname); int login(UserInfo* table, char* uname, char* password); int logout(UserInfo* table, char* uname); int protect_realm(UserInfo* table, char* uname, char* password); @@ -18,3 +20,4 @@ int load_cart(UserInfo* table, char* uname, char* cart_name); int enter_realm(UserInfo* table, char* uname, char* realm_name); int leave_realm(UserInfo* table, char* uname); int unload_cart(UserInfo* table, char* uname); +int delete_realm(UserInfo* table, char* uname, char* realm); \ No newline at end of file diff --git a/util.c b/util.c index 0024e87..6e4b66c 100644 --- a/util.c +++ b/util.c @@ -1,4 +1,5 @@ #include +#include #include #include #include "cart.h" @@ -149,6 +150,59 @@ Blob* read_chars(char* path) { } } +int issymlink(char* name) { + struct stat s; + return lstat(name, &s) >= 0 && S_ISLNK(s.st_mode); +} + +int rm_dir(char* f) { + char* name; + int fd, i, j, n, ndir, nname; + Dir* dirbuf; + int fail = 0; + + fd = open(f, OREAD); + if (fd < 0) { + return 0; + } + n = dirreadall(fd, &dirbuf); + close(fd); + if (n < 0) { + return 0; + } + + nname = strlen(f) + 1 + STATMAX + 1; /* plenty! */ + name = malloc(nname); + if (name == 0) { + return 0; + } + + ndir = 0; + for (i = 0; i < n; i++) { + snprint(name, nname, "%s/%s", f, dirbuf[i].name); + if (remove(name) != -1 || issymlink(name)) + dirbuf[i].qid.type = QTFILE; /* so we won't recurse */ + else { + if (dirbuf[i].qid.type & QTDIR) + ndir++; + else + fail = 1; + } + } + if (ndir) + for (j = 0; j < n; j++) + if (dirbuf[j].qid.type & QTDIR) { + snprint(name, nname, "%s/%s", f, dirbuf[j].name); + rmdir(name); + } + if (remove(f) == -1) + fail = 1; + + free(name); + free(dirbuf); + return fail ? 0 : 1; +} + void strreverse(char* begin, char* end) { char aux; diff --git a/util.h b/util.h index d61be94..e2182f5 100644 --- a/util.h +++ b/util.h @@ -16,4 +16,5 @@ int ssin(char* str, char* substr); char* ccat(char* dst, char c); Blob* read_bytes(char* path); Blob* read_chars(char* path); -void itoa(int, char*, int); \ No newline at end of file +void itoa(int, char*, int); +int rm_dir(char* path); \ No newline at end of file diff --git a/xrxs.c b/xrxs.c index d55adf9..f1ed0a1 100644 --- a/xrxs.c +++ b/xrxs.c @@ -7,6 +7,7 @@ #include #include #include +#include "config.h" #include "err.h" #include "command.h" #include "util.h" @@ -16,14 +17,11 @@ #include "realm.h" #include "user.h" -#define CARTSPATH "./carts" -#define REALMSPATH "./realms" - int chatty9p = 1; static Tree* tree; -static UserInfo users_table[64]; +static UserInfo users_table[MAX_USERS]; void xrxs_attach(Req* r) { /* As it is, once the user detaches, they will stay in the table @@ -36,7 +34,7 @@ void xrxs_attach(Req* r) { char* usr; char* username = r->ifcall.uname; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { usr = users_table[i].name; if (scmp(usr, username)) { respond(r, EUNAME); @@ -44,6 +42,7 @@ void xrxs_attach(Req* r) { } if (*usr == 0) { scpy(username, usr, 32); + users_table[i].random = -1; vacancy = 1; break; } @@ -59,6 +58,7 @@ void write_ctl(Req* r) { char cmd[16] = {0}; char* c = r->ifcall.data; int i; + int* code = ctl_code_handle(users_table, r->fid->uid); for (i = 0; i < r->ifcall.count && *c != ' ' && *c != '\n'; i++) { ccat(cmd, *c++); @@ -71,28 +71,31 @@ void write_ctl(Req* r) { uvlong const cmd_hashv = hash(cmd, 0); switch (cmd_hashv) { case LOGIN: - login(users_table, r->fid->uid, c); + *code = login(users_table, r->fid->uid, c); break; case LOAD: - load_cart(users_table, r->fid->uid, c); + *code = load_cart(users_table, r->fid->uid, c); break; case CHUNK: - get_chunk(users_table, r->fid->uid, c); + *code = get_chunk(users_table, r->fid->uid, c); break; case CREATE: - create_realm(users_table, r->fid->uid, c); + *code = create_realm(users_table, r->fid->uid, c) != nil ? 1 : 0; break; case PROTECT: - protect_realm(users_table, r->fid->uid, c); + *code = protect_realm(users_table, r->fid->uid, c); break; case TRANSFER: - transfer_realm(users_table, r->fid->uid, c); + *code = transfer_realm(users_table, r->fid->uid, c); + break; + case DELETE: + *code = delete_realm(users_table, r->fid->uid, c); break; case ENTER: - enter_realm(users_table, r->fid->uid, c); + *code = enter_realm(users_table, r->fid->uid, c); break; case LEAVE: - leave_realm(users_table, r->fid->uid); + *code = leave_realm(users_table, r->fid->uid); break; case LOGOUT: logout(users_table, r->fid->uid); @@ -104,7 +107,7 @@ void write_ctl(Req* r) { // reset(r->fid->uid); break; case UNLOAD: - unload_cart(users_table, r->fid->uid); + *code = unload_cart(users_table, r->fid->uid); break; } r->ofcall.count = r->ifcall.count; @@ -189,26 +192,27 @@ void xrxs_write(Req* r) { } void read_users(Req* r) { - char buf[2113] = {0}; + String* data = s_new(); int i; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if (scmp(users_table[i].name, r->fid->uid)) { - scat(buf, users_table[i].name); - ccat(buf, '\n'); + s_append(data, users_table[i].name); + s_putc(data, '\n'); break; } } - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if ( scmp(users_table[i].name, "\0") || scmp(users_table[i].name, r->fid->uid)) { continue; } - scat(buf, users_table[i].name); - ccat(buf, '\n'); + s_append(data, users_table[i].name); + s_putc(data, '\n'); } - ccat(buf, 0); - readstr(r, buf); + s_terminate(data); + readstr(r, data->base); + s_free(data); respond(r, nil); } @@ -250,8 +254,24 @@ void s_freemany(String** ss) { free(ss); } +void read_ctl(Req* r) { + int* code = ctl_code_handle(users_table, r->fid->uid); + char buf[8] = {0}; + if (code != nil) { + if (*code) { + strcat(buf, "1\n"); + } else { + strcat(buf, "0\n"); + } + } else { + strcat(buf, "-1\n"); + } + readstr(r, buf); + respond(r, nil); +} + void read_carts(Req* r) { - String** carts = list_dir(CARTSPATH); + String** carts = list_dir(DATA_DIR); String** c = carts; String* data = s_new(); while (*c != nil) { @@ -318,7 +338,7 @@ end: void read_realms(Req* r) { UserInfo* user = find_user(users_table, r->fid->uid); - char realm_path[128] = {0}; + char realm_path[256] = {0}; String** realms; String** rr; Realm* realm; @@ -346,7 +366,7 @@ void read_realms(Req* r) { m = realm->max; p = realm->password ? 1 : 0; - for (i = u = 0; i < 64; i++) { + for (i = u = 0; i < MAX_USERS; i++) { if ( users_table[i].realm != nil && scmp(users_table[i].realm->name, realm->name)) @@ -463,22 +483,24 @@ void read_grandom(Req* r) { int reset = 1; int random; UserInfo* u = find_user(users_table, r->fid->uid); - UserInfo** usrs = malloc(64 * sizeof(UserInfo*)); + UserInfo** usrs = malloc(MAX_USERS * sizeof(UserInfo*)); UserInfo* p = users_table; UserInfo** uu = usrs; - if (u->realm == nil) + if (u->realm == nil) { + respond(r, nil); return; + } - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if (scmp(p->realm->name, u->realm->name)) { *uu++ = p; - if (i < 64) + if (i < MAX_USERS - 1) *uu = nil; } } uu = usrs; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if ((*uu) != nil && (*uu)->random >= 0) { reset = 0; break; @@ -489,7 +511,7 @@ void read_grandom(Req* r) { srand(rand()); random = rand() % 100; uu = usrs; - for (i = 0; i < 64; i++) { + for (i = 0; i < MAX_USERS; i++) { if ((*uu) != nil) { (*uu)->random = random; } @@ -505,6 +527,9 @@ void read_grandom(Req* r) { void xrxs_read(Req* r) { Aux* a = r->fid->file->aux; switch (a->type) { + case CTL: + read_ctl(r); + break; case USERS: read_users(r); break; @@ -586,7 +611,7 @@ void threadmain(int argc, char* argv[]) { tree = fs.tree; closefile( - createfile(tree->root, "ctl", nil, DMAPPEND | 0300, create_aux(CTL))); + createfile(tree->root, "ctl", nil, DMAPPEND | 0600, create_aux(CTL))); closefile(createfile(tree->root, "carts", nil, 0400, create_aux(CARTS))); closefile(createfile(tree->root, "users", nil, 0400, create_aux(USERS))); closefile(createfile(tree->root, "slot", nil, 0400, create_aux(SLOT)));