From cb8af6e7bf5f6e70fc9722d36448213c719e83c8 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Thu, 19 May 2016 16:47:06 -0400 Subject: implement more of dunnet --- src/SOURCES | 1 + src/client.c | 59 +++++++++++++++------------- src/client.h | 1 + src/client_reqs.c | 7 +++- src/globals.h | 5 --- src/room.c | 14 ++++++- src/room.h | 9 +++-- src/server.c | 10 ++++- src/server.h | 4 ++ src/server_reqs.c | 44 ++++++++++++++++++--- src/server_reqs.h | 8 +++- src/userdb.c | 13 +++++++ src/userdb.h | 4 ++ src/util.c | 14 +++++++ src/util.h | 8 ++++ src/world.c | 11 +++++- src/world.h | 4 ++ src/world_api.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 283 insertions(+), 46 deletions(-) create mode 100644 src/world_api.c (limited to 'src') diff --git a/src/SOURCES b/src/SOURCES index 5cb3c9b..30204ab 100644 --- a/src/SOURCES +++ b/src/SOURCES @@ -13,3 +13,4 @@ userdb.c util.c verb.c world.c +world_api.c diff --git a/src/client.c b/src/client.c index 7c69708..e06058d 100644 --- a/src/client.c +++ b/src/client.c @@ -38,6 +38,8 @@ static volatile sig_atomic_t output_locked = 0; char *current_user = NULL; +bool child_rawmode = false; + bool poll_requests(void); void out_raw(const void *buf, size_t len) @@ -160,6 +162,7 @@ tryagain: if(len <= 0) error("lost connection"); + /* null-terminate */ buf[CLIENT_READ_SZ - 1] = '\0'; enum telnet_status ret = telnet_parse_data((unsigned char*)buf + bufidx, len); @@ -609,43 +612,47 @@ auth: while(1) { - out(">> "); + if(!child_rawmode) + out(">> "); char *line = client_read(); char *orig = strdup(line); char *save = NULL; - char *tok = strtok_r(line, WSPACE, &save); + if(!child_rawmode) + { + char *tok = strtok_r(line, WSPACE, &save); - if(!tok) - goto next_cmd; + if(!tok) + goto next_cmd; - all_upper(tok); + all_upper(tok); - const struct client_cmd *cmd = hash_lookup(cmd_map, tok); - if(cmd && cmd->cb && (!cmd->admin_only || (cmd->admin_only && are_admin))) - { - int ret = cmd->cb(&save); - switch(ret) + const struct client_cmd *cmd = hash_lookup(cmd_map, tok); + if(cmd && cmd->cb && (!cmd->admin_only || (cmd->admin_only && are_admin))) + { + int ret = cmd->cb(&save); + switch(ret) + { + case CMD_OK: + goto next_cmd; + case CMD_LOGOUT: + free(line); + free(orig); + goto auth; + case CMD_QUIT: + free(line); + free(orig); + goto done; + default: + error("client: bad callback return value"); + } + } + else if(cmd && cmd->admin_only && !are_admin) { - case CMD_OK: + out("You are not allowed to do that.\n"); goto next_cmd; - case CMD_LOGOUT: - free(line); - free(orig); - goto auth; - case CMD_QUIT: - free(line); - free(orig); - goto done; - default: - error("client: bad callback return value"); } } - else if(cmd && cmd->admin_only && !are_admin) - { - out("You are not allowed to do that.\n"); - goto next_cmd; - } /* if we can't handle it, let the master process try */ send_master(REQ_EXECVERB, orig, strlen(orig) + 1); diff --git a/src/client.h b/src/client.h index 223bf86..0bd74bb 100644 --- a/src/client.h +++ b/src/client.h @@ -23,6 +23,7 @@ extern int client_fd, to_parent, from_parent; extern bool are_admin; +extern bool child_rawmode; /* call from child process ONLY */ void send_master(unsigned char cmd, const void *data, size_t sz); diff --git a/src/client_reqs.c b/src/client_reqs.c index 21174c4..a7e6a01 100644 --- a/src/client_reqs.c +++ b/src/client_reqs.c @@ -56,7 +56,7 @@ bool poll_requests(void) /* parent closed pipe */ if(!packetlen) { - debugf("master process died\n"); + debugf("master process died, dropping client.\n"); exit(0); } @@ -66,6 +66,11 @@ bool poll_requests(void) switch(cmd) { + case REQ_RAWMODE: + { + child_rawmode = !child_rawmode; + break; + } case REQ_BCASTMSG: { out((char*)data, datalen); diff --git a/src/globals.h b/src/globals.h index 6e050ee..100ec1d 100644 --- a/src/globals.h +++ b/src/globals.h @@ -55,11 +55,6 @@ #include #include -/* convenience macros */ -#define ARRAYLEN(x) (sizeof(x)/sizeof(x[0])) -#define MAX(a,b) (((a)>(b))?(a):(b)) -#define MIN(a,b) (((a)<(b))?(a):(b)) - /* global constants */ #define USERFILE "users.dat" #define WORLDFILE "world.dat" diff --git a/src/room.c b/src/room.c index 2e7688a..19a4b73 100644 --- a/src/room.c +++ b/src/room.c @@ -25,6 +25,8 @@ #include "userdb.h" #include "world.h" +/* these do not perform hook checking on the requested action, that is + * the responsibility of the caller */ bool room_user_add(room_id id, struct child_data *child) { struct room_t *room = room_get(id); @@ -60,6 +62,16 @@ bool room_user_del(room_id id, struct child_data *child) return false; } +/* ignores hooks */ +void room_user_teleport(struct child_data *child, room_id id) +{ + if(child->room != id) + { + room_user_del(child->room, child); + room_user_add(id, child); + } +} + void room_free(struct room_t *room) { if(room->data.hook_destroy) @@ -99,7 +111,7 @@ bool room_obj_add(room_id room, struct object_t *obj) return status; } -bool room_obj_add_alias(room_id room, struct object_t *obj, char *alias) +bool room_obj_add_alias(room_id room, struct object_t *obj, const char *alias) { /* the primary name must be added */ if(!room_obj_get(room, obj->name)) diff --git a/src/room.h b/src/room.h index c0304c7..62901c8 100644 --- a/src/room.h +++ b/src/room.h @@ -69,15 +69,19 @@ struct room_t { /* hash maps */ void *objects; /* multimap of object name -> object */ - void *verbs; + void *verbs; /* name -> verb_t */ void *users; /* username -> child_data */ void *userdata; }; /* room/world */ + +/* none of these care about room hooks, which are the caller's + * responsibility */ bool room_user_add(room_id id, struct child_data *child); bool room_user_del(room_id id, struct child_data *child); +void room_user_teleport(struct child_data *child, room_id id); /* On the first call, room should be a valid room id, and *save should * point to a void pointer. On subsequent calls, room should be @@ -92,10 +96,9 @@ const struct multimap_list *room_obj_iterate(room_id room, void **save, size_t * */ bool room_obj_add(room_id room, struct object_t *obj); -bool room_obj_add_alias(room_id room, struct object_t *obj, char *alias); +bool room_obj_add_alias(room_id room, struct object_t *obj, const char *alias); bool room_obj_del(room_id room, const char *name); -bool room_obj_del_alias(room_id room, struct object_t *obj, const char *alias); bool room_obj_del_by_ptr(room_id room, struct object_t *obj); const struct multimap_list *room_obj_get(room_id room, const char *obj); diff --git a/src/server.c b/src/server.c index b736124..9a28679 100644 --- a/src/server.c +++ b/src/server.c @@ -48,7 +48,7 @@ static char *module_handle = NULL; /* save after every X changes to the world state */ #define SAVE_INTERVAL 10 -/* saves state periodically */ +/* saves game state periodically */ void server_save_state(bool force) { if(!are_child) @@ -231,6 +231,9 @@ static void load_worldfile(void) } } + netcosm_write_userdata_cb = dlsym(module_handle, "netcosm_write_userdata_cb"); + netcosm_read_userdata_cb = dlsym(module_handle, "netcosm_read_userdata_cb"); + if(access(WORLDFILE, F_OK) < 0) { world_init(netcosm_world, netcosm_world_sz, netcosm_world_name); @@ -484,6 +487,11 @@ static void parse_args(int argc, char *argv[]) } } } + else + { + debugf("Unknown argument `%s'\n", argv[i]); + exit(0); + } } } diff --git a/src/server.h b/src/server.h index 6d3d029..a0efb39 100644 --- a/src/server.h +++ b/src/server.h @@ -24,6 +24,7 @@ enum room_id; /* everything the server needs to manage its children */ +/* aliased as user_t */ struct child_data { pid_t pid; @@ -40,6 +41,9 @@ struct child_data { ev_io *io_watcher; ev_child *sigchld_watcher; + /* raw mode callback (NULL if none, set by world module) */ + void (*raw_mode_cb)(struct child_data*, char *data, size_t len); + /* remote IP */ struct in_addr addr; }; diff --git a/src/server_reqs.c b/src/server_reqs.c index ab283ca..ed2d09c 100644 --- a/src/server_reqs.c +++ b/src/server_reqs.c @@ -26,18 +26,29 @@ #include "world.h" /* sends a single packet to a child, mostly reliable */ + +/* splits REQ_BCASTMSG message into multiple packets if data length + * exceeds MSG_MAX, however, other requests will not be split and will + * cause a failed assertion */ + static void send_packet(struct child_data *child, unsigned char cmd, const void *data, size_t datalen) { - assert(datalen < MSG_MAX); + assert(datalen < MSG_MAX || cmd == REQ_BCASTMSG); unsigned char pkt[MSG_MAX]; pkt[0] = cmd; - //if((data?datalen:0) + 1 > MSG_MAX && cmd == REQ_BCASTMSG) - //{ - // /* TODO: split long messages */ - // ; - //} + if(cmd == REQ_BCASTMSG && (data?datalen:0) + 1 > MSG_MAX) + { + /* split long messages */ + const char *ptr = data, *stop = (const char*)data + datalen; + while(ptr < stop) + { + send_packet(child, cmd, ptr, MIN(stop - ptr, MSG_MAX - 1)); + ptr += MSG_MAX - 1; + } + return; + } if(data && datalen) memcpy(pkt + 1, data, datalen); @@ -50,6 +61,19 @@ tryagain: } } +void child_toggle_rawmode(struct child_data *child, void (*cb)(user_t*, char *data, size_t len)) +{ + if(!are_child) + { + send_packet(child, REQ_RAWMODE, NULL, 0); + /* this pointer also indicates whether raw mode is on */ + if(!child->raw_mode_cb) + child->raw_mode_cb = cb; + else + child->raw_mode_cb = NULL; + } +} + void __attribute__((format(printf,2,3))) send_msg(struct child_data *child, const char *fmt, ...) { va_list ap; @@ -495,6 +519,14 @@ static void req_listusers(unsigned char *data, size_t datalen, struct child_data static void req_execverb(unsigned char *data, size_t datalen, struct child_data *sender) { (void) datalen; + + /* if the child is in raw mode, pass the data to the world module */ + if(sender->raw_mode_cb) + { + sender->raw_mode_cb(sender, (char*)data, datalen); + return; + } + /* first look for a room-local verb */ char *save; char *tok = strtok_r((char*)data, " \t", &save); diff --git a/src/server_reqs.h b/src/server_reqs.h index 10c1755..8db6927 100644 --- a/src/server_reqs.h +++ b/src/server_reqs.h @@ -50,7 +50,7 @@ #define REQ_DROP 22 /* server: drop user object if allowed */ #define REQ_LISTUSERS 23 /* server: list users in USERFILE */ #define REQ_EXECVERB 24 /* server: execute a verb with its arguments */ -#define REQ_RAWMODE 25 /* child: toggle the child's processing of commands and instead sending input directly to master */ +#define REQ_RAWMODE 25 /* child: toggle the child's processing of commands and instead send input directly to master */ /* child states, sent as an int to the master */ #define STATE_INIT 0 /* initial state */ @@ -66,3 +66,9 @@ void reqmap_init(void); void reqmap_free(void); void send_msg(user_t *child, const char *fmt, ...) __attribute__((format(printf,2,3))); + +/* toggle the child into "raw mode": all commands typed by the + * connected client will be send to the world module; + * if the child is already in raw mode the callback is ignored */ + +void child_toggle_rawmode(user_t *child, void (*cb)(user_t*, char *data, size_t len)); diff --git a/src/userdb.c b/src/userdb.c index 0ebe159..14e9fd8 100644 --- a/src/userdb.c +++ b/src/userdb.c @@ -26,6 +26,7 @@ #include "server.h" #include "server_reqs.h" #include "userdb.h" +#include "world.h" static void *map = NULL; static char *db_file = NULL; @@ -101,6 +102,10 @@ void userdb_init(const char *file) } } + /* now we read in the world module's data, if possible */ + if(netcosm_read_userdata_cb) + data->userdata = netcosm_read_userdata_cb(fd); + hash_insert(map, data->username, data); } @@ -124,8 +129,10 @@ bool userdb_write(const char *file) if(!user) break; + /* dump structure */ write(fd, user, sizeof(*user)); + /* now go back and write what the pointers are pointing at */ size_t n_objects; if(user->objects) n_objects = obj_count_noalias(user->objects); @@ -158,6 +165,12 @@ bool userdb_write(const char *file) } } } + + /* write world module's data, if possible */ + if(netcosm_write_userdata_cb) + { + netcosm_write_userdata_cb(fd, user->userdata); + } } close(fd); diff --git a/src/userdb.h b/src/userdb.h index 25bdc7b..7958fb3 100644 --- a/src/userdb.h +++ b/src/userdb.h @@ -39,12 +39,16 @@ struct userdata_t { time_t last_login; void *objects; /* multihash of object names -> objects */ + + /* for use by world module */ + void *userdata; }; /* call before using anything else */ void userdb_init(const char *dbfile); /* looks up a username in the DB, returns NULL upon failure */ +/* changes made to the returned structure will persist */ struct userdata_t *userdb_lookup(const char *username); bool userdb_remove(const char *username); diff --git a/src/util.c b/src/util.c index 9c4b447..c5b606c 100644 --- a/src/util.c +++ b/src/util.c @@ -173,6 +173,20 @@ void write_size(int fd, size_t b) error("write failed"); } +int read_int(int fd) +{ + int ret; + if(read(fd, &ret, sizeof(ret)) != sizeof(ret)) + error("unexpected EOF"); + return ret; +} + +void write_int(int fd, int b) +{ + if(write(fd, &b, sizeof(b)) != sizeof(b)) + error("write failed"); +} + bool is_vowel(char c) { switch(tolower(c)) diff --git a/src/util.h b/src/util.h index 8007ec8..a6965be 100644 --- a/src/util.h +++ b/src/util.h @@ -23,6 +23,11 @@ #define WSPACE " \t\r\n" +/* convenience macros */ +#define ARRAYLEN(x) (sizeof(x)/sizeof(x[0])) +#define MAX(a,b) (((a)>(b))?(a):(b)) +#define MIN(a,b) (((a)<(b))?(a):(b)) + /* utility functions */ void __attribute__((noreturn,format(printf,1,2))) error(const char *fmt, ...); void __attribute__((format(printf,4,5))) debugf_real(const char*, int, const char*, const char *fmt, ...); @@ -48,6 +53,9 @@ uint64_t read_uint64(int fd); void write_size(int fd, size_t); size_t read_size(int fd); +void write_int(int fd, int i); +int read_int(int fd); + bool is_vowel(char c); size_t strlcat(char *dst, const char *src, size_t siz); diff --git a/src/world.c b/src/world.c index d0e1711..45a1717 100644 --- a/src/world.c +++ b/src/world.c @@ -39,6 +39,9 @@ size_t netcosm_world_sz; void (*netcosm_world_simulation_cb)(void) = NULL; unsigned netcosm_world_simulation_interval = 0; +void (*netcosm_write_userdata_cb)(int fd, void *ptr) = NULL; +void *(*netcosm_read_userdata_cb)(int fd) = NULL; + const char *netcosm_world_name; /* processed world data */ @@ -188,8 +191,6 @@ void world_free(void) free(world); world = NULL; } - if(sim_timer) - free(sim_timer); } static void start_sim_callback(void) @@ -440,6 +441,12 @@ bool world_verb_add(struct verb_t *verb) return !hash_insert(verb_map, verb->name, verb); } +bool world_verb_del(struct verb_t *verb) +{ + init_map(); + return hash_remove(verb_map, verb->name); +} + void *world_verb_map(void) { init_map(); diff --git a/src/world.h b/src/world.h index 4ce253e..f850876 100644 --- a/src/world.h +++ b/src/world.h @@ -45,6 +45,10 @@ extern size_t netcosm_world_sz; extern void (*netcosm_world_simulation_cb)(void); extern unsigned netcosm_world_simulation_interval; +/* user data callback */ +extern void (*netcosm_write_userdata_cb)(int fd, void *ptr); +extern void* (*netcosm_read_userdata_cb)(int fd); + extern const char *netcosm_world_name; #endif diff --git a/src/world_api.c b/src/world_api.c new file mode 100644 index 0000000..f38f892 --- /dev/null +++ b/src/world_api.c @@ -0,0 +1,113 @@ +/* + * NetCosm - a MUD server + * Copyright (C) 2016 Franklin Wei + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "globals.h" + +#include "server.h" +#include "server_reqs.h" +#include "hash.h" +#include "multimap.h" +#include "userdb.h" +#include "world.h" +#include "world_api.h" + +static const struct world_api api = { + obj_new, + obj_dup, + obj_copy, + obj_free, + room_user_teleport, + room_obj_add, + room_obj_add_alias, + room_obj_del, + room_obj_del_by_ptr, + room_obj_get, + room_obj_get_size, + room_obj_count, + room_obj_count_noalias, + room_verb_add, + room_verb_del, + room_verb_map, + room_get, + room_get_id, + world_verb_add, + world_verb_del, + world_verb_map, + verb_new, + verb_free, + hash_djb, + compare_strings, + compare_strings_nocase, + hash_init, + hash_setfreedata_cb, + hash_setfreekey_cb, + hash_free, + hash_insert, + hash_overwrite, + hash_lookup, + hash_remove, + hash_iterate, + hash_insert_pairs, + hash_getkeyptr, + hash_dup, + hash_setdupdata_cb, + multimap_init, + multimap_free, + multimap_lookup, + multimap_insert, + multimap_delete, + multimap_delete_all, + multimap_iterate, + multimap_size, + multimap_setfreedata_cb, + multimap_dup, + multimap_setdupdata_cb, + multimap_copy, + send_msg, + child_toggle_rawmode, + userdb_lookup, + userdb_remove, + userdb_size, + userdb_add, + userdb_iterate, + userdb_add_obj, + userdb_del_obj, + userdb_del_obj_by_ptr, + error, + all_upper, + all_lower, + write_string, + read_string, + write_roomid, + read_roomid, + write_bool, + read_bool, + write_uint32, + read_uint32, + write_uint64, + read_uint64, + write_size, + read_size, + write_int, + read_int, + is_vowel, + strlcat, + format_noun +}; + +const struct world_api *nc = &api; -- cgit v1.1