aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFranklin Wei <git@fwei.tk>2016-03-28 14:11:22 -0400
committerFranklin Wei <git@fwei.tk>2016-03-31 17:09:36 -0400
commited95a5621ac9c4f5002e68a981f8b24d5caaedf4 (patch)
treeaee87cf74280e8f9dd29aca88d12a638f37b2493 /src
parent13052597a19fd2212efb7f51f19ed73b9f4b6ba4 (diff)
downloadnetcosm-ed95a5621ac9c4f5002e68a981f8b24d5caaedf4.zip
netcosm-ed95a5621ac9c4f5002e68a981f8b24d5caaedf4.tar.gz
netcosm-ed95a5621ac9c4f5002e68a981f8b24d5caaedf4.tar.bz2
netcosm-ed95a5621ac9c4f5002e68a981f8b24d5caaedf4.tar.xz
kludge things to compile on old linux
Diffstat (limited to 'src')
-rw-r--r--src/auth.c5
-rw-r--r--src/auth.h6
-rw-r--r--src/client.c70
-rw-r--r--src/client.h4
-rw-r--r--src/client_reqs.c14
-rw-r--r--src/client_reqs.h2
-rw-r--r--src/globals.h5
-rw-r--r--src/hash.c1
-rw-r--r--src/obj.c6
-rw-r--r--src/obj.h5
-rw-r--r--src/room.h22
-rw-r--r--src/server.c48
-rw-r--r--src/server.h7
-rw-r--r--src/server_reqs.c56
-rw-r--r--src/server_reqs.h7
-rw-r--r--src/userdb.c5
-rw-r--r--src/util.c90
-rw-r--r--src/util.h7
-rw-r--r--src/verb.h4
-rw-r--r--src/world.c50
-rw-r--r--src/world.h6
21 files changed, 320 insertions, 100 deletions
diff --git a/src/auth.c b/src/auth.c
index 069e9fe..c7b53d3 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -193,11 +193,12 @@ struct userdata_t *auth_check(const char *name2, const char *pass2)
{
debugf("auth module: user %s found\n", name2);
- /* hashes are in HEX to avoid the Trucha bug */
+ /* hashes are in lowercase hex to avoid the Trucha bug
+ * but still allow comparison with strcmp() */
char *new_hash_hex = hash_pass_hex(pass, salt);
bool success = true;
- /* constant-time comparison to a timing attack */
+ /* constant-time comparison to hopefully prevent a timing attack */
for(int i = 0; i < AUTH_HASHLEN; ++i)
{
if(new_hash_hex[i] != hash[i])
diff --git a/src/auth.h b/src/auth.h
index ba24595..c5c9dc1 100644
--- a/src/auth.h
+++ b/src/auth.h
@@ -26,12 +26,6 @@
//#define HASH_ITERS 500000
#define HASH_ITERS 1
-struct authinfo_t {
- bool success;
- const char *user;
- int authlevel;
-};
-
/* makes admin account */
void first_run_setup(void);
diff --git a/src/client.c b/src/client.c
index a93ae0e..d66836e 100644
--- a/src/client.c
+++ b/src/client.c
@@ -20,10 +20,12 @@
#include "auth.h"
#include "client.h"
+#include "client_reqs.h"
#include "hash.h"
#include "server.h"
#include "room.h"
#include "telnet.h"
+#include "userdb.h"
#include "util.h"
bool are_admin = false;
@@ -100,6 +102,8 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...)
if(is_newline)
++ptr; /* skip the newline */
+
+ /* skip following spaces */
while(*ptr == ' ')
++ptr;
last_space = 0;
@@ -184,7 +188,7 @@ tryagain:
}
}
-/* still not encrypted, but a bit more secure than echoing the password! */
+/* still not encrypted, but a bit better than echoing the password! */
char *client_read_password(void)
{
telnet_echo_off();
@@ -194,8 +198,6 @@ char *client_read_password(void)
return ret;
}
-#define WSPACE " \t\r\n"
-
#define CMD_OK 0
#define CMD_LOGOUT 1
#define CMD_QUIT 2
@@ -206,7 +208,10 @@ int user_cb(char **save)
{
char *what = strtok_r(NULL, WSPACE, save);
if(!what)
+ {
+ out("Usage: USER <ADD|DEL|MODIFY|LIST> <ARGS>\n");
return CMD_OK;
+ }
all_upper(what);
@@ -304,9 +309,9 @@ int client_cb(char **save)
else if(!strcmp(what, "KICK"))
{
char *pid_s = strtok_r(NULL, WSPACE, save);
- all_upper(pid_s);
if(pid_s)
{
+ all_upper(pid_s);
if(!strcmp(pid_s, "ALL"))
{
const char *msg = "Kicking everyone...\n";
@@ -336,7 +341,7 @@ int client_cb(char **save)
debugf("Success.\n");
}
else
- out("Usage: CLIENT KICK <PID>\n");
+ out("Usage: CLIENT KICK <PID|ALL>\n");
}
return CMD_OK;
}
@@ -402,6 +407,7 @@ int take_cb(char **save)
int wait_cb(char **save)
{
(void) save;
+ /* debugging */
send_master(REQ_WAIT, NULL, 0);
return CMD_OK;
}
@@ -416,14 +422,61 @@ int go_cb(char **save)
client_look();
}
else
- out("Expected direction after GO.\n");
+ out("I don't understand where you want me to go.\n");
return CMD_OK;
}
int drop_cb(char **save)
{
char *what = strtok_r(NULL, "", save);
- client_drop(what);
+ if(what)
+ client_drop(what);
+ else
+ out("You must supply an object.\n");
+ return CMD_OK;
+}
+
+int chpass_cb(char **save)
+{
+ out("Enter current password: ");
+ char *current = client_read_password();
+
+ struct userdata_t *current_data = auth_check(current_user, current);
+
+ memset(current, 0, strlen(current));
+ free(current);
+
+ if(!current_data)
+ {
+ out("Password mismatch.\n");
+ return CMD_OK;
+ }
+
+ out("Enter new password: ");
+ char *pass1 = client_read_password();
+
+ out("Retype new password: ");
+
+ char *pass2 = client_read_password();
+
+ if(strcmp(pass1, pass2))
+ {
+ memset(pass1, 0, strlen(pass1));
+ memset(pass2, 0, strlen(pass2));
+ free(pass1);
+ free(pass2);
+
+ out("Passwords do not match.\n");
+ return CMD_OK;
+ }
+
+ memset(pass2, 0, strlen(pass2));
+ free(pass2);
+
+ auth_user_add(current_user, pass1, current_data->priv);
+
+ memset(pass1, 0, strlen(pass1));
+
return CMD_OK;
}
@@ -442,9 +495,10 @@ static const struct client_cmd {
{ "LOOK", look_cb, false },
{ "INVENTORY", inventory_cb, false },
{ "TAKE", take_cb, false },
- { "WAIT", wait_cb, false },
+ { "WAIT", wait_cb, true },
{ "GO", go_cb, false },
{ "DROP", drop_cb, false },
+ { "CHPASS", chpass_cb, false },
};
static void *cmd_map = NULL;
diff --git a/src/client.h b/src/client.h
index 5b5c5ff..223bf86 100644
--- a/src/client.h
+++ b/src/client.h
@@ -21,10 +21,6 @@
#include "globals.h"
-#include "client_reqs.h"
-#include "room.h"
-#include "userdb.h"
-
extern int client_fd, to_parent, from_parent;
extern bool are_admin;
diff --git a/src/client_reqs.c b/src/client_reqs.c
index 39a67f9..21174c4 100644
--- a/src/client_reqs.c
+++ b/src/client_reqs.c
@@ -19,6 +19,7 @@
#include "globals.h"
#include "client.h"
+#include "client_reqs.h"
#include "hash.h"
enum reqdata_typespec reqdata_type = TYPE_NONE;
@@ -81,9 +82,7 @@ bool poll_requests(void)
int status = *((int*)data);
reqdata_type = TYPE_BOOLEAN;
- returned_reqdata.boolean = status;
- if(!status)
- out("You cannot go that way.\n");
+ returned_reqdata.boolean = (status == 1);
break;
}
case REQ_GETUSERDATA:
@@ -266,8 +265,13 @@ void client_look_at(char *obj)
void client_take(char *obj)
{
- all_lower(obj);
- send_master(REQ_TAKE, obj, strlen(obj) + 1);
+ if(obj)
+ {
+ all_lower(obj);
+ send_master(REQ_TAKE, obj, strlen(obj) + 1);
+ }
+ else
+ out("You must supply an object.\n");
}
void client_inventory(void)
diff --git a/src/client_reqs.h b/src/client_reqs.h
index 979da9a..0d2a63c 100644
--- a/src/client_reqs.h
+++ b/src/client_reqs.h
@@ -24,6 +24,8 @@
#include "server_reqs.h"
#include "userdb.h"
+enum room_id;
+
enum reqdata_typespec { TYPE_NONE = 0, TYPE_USERDATA, TYPE_BOOLEAN } reqdata_type;
union reqdata_t {
diff --git a/src/globals.h b/src/globals.h
index 987d382..5a164ba 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -22,16 +22,17 @@
#define _GNU_SOURCE
#include <ev.h>
+
#include <openssl/sha.h>
#include <openssl/opensslv.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <assert.h>
-#include <bsd/string.h> // for strlcat
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
@@ -66,7 +67,7 @@
#define WORLD_MAGIC 0x31415926
#define USERDB_MAGIC 0x27182818
#define MAX_FAILURES 3
-#define NETCOSM_VERSION "0.5.0"
+#define NETCOSM_VERSION "0.5.1"
/* username length */
#define MAX_NAME_LEN 32
diff --git a/src/hash.c b/src/hash.c
index 290c668..ce91ceb 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -357,6 +357,7 @@ struct hash_export_node hash_get_internal_node(void *ptr, const void *key)
}
struct hash_export_node ret;
+ memset(&ret, 0, sizeof(ret));
ret.node = NULL;
return ret;
}
diff --git a/src/obj.c b/src/obj.c
index deeb30d..296fbb5 100644
--- a/src/obj.c
+++ b/src/obj.c
@@ -144,7 +144,7 @@ struct object_t *obj_copy(struct object_t *obj)
struct object_t *obj_dup(struct object_t *obj)
{
- debugf("Adding an object reference to #%lu.\n", obj->id);
+ debugf("Adding an object reference to #%" PRI_OBJID ".\n", obj->id);
++obj->refcount;
return obj;
}
@@ -154,11 +154,11 @@ void obj_free(void *ptr)
struct object_t *obj = ptr;
--obj->refcount;
- debugf("Freeing an object reference for #%lu (%s, %d).\n", obj->id, obj->name, obj->refcount);
+ debugf("Freeing an object reference for #%" PRI_OBJID" (%s, %d).\n", obj->id, obj->name, obj->refcount);
if(!obj->refcount)
{
- debugf("Freeing object #%lu\n", obj->id);
+ debugf("Freeing object #%"PRI_OBJID"\n", obj->id);
if(obj->class->hook_destroy)
obj->class->hook_destroy(obj);
diff --git a/src/obj.h b/src/obj.h
index 121e3ab..3858d78 100644
--- a/src/obj.h
+++ b/src/obj.h
@@ -21,14 +21,13 @@
#include "globals.h"
#include "room.h"
+#include "server.h"
/* Objects belong to an object class. Objects define their object
* class through the class name, which is converted to a class ID
* internally.
*/
-typedef struct child_data user_t;
-
struct object_t;
struct obj_class_t {
@@ -55,6 +54,8 @@ struct obj_class_t {
typedef uint64_t obj_id;
+#define PRI_OBJID PRId64
+
struct obj_alias_t {
char *alias;
struct obj_alias_t *next;
diff --git a/src/room.h b/src/room.h
index e9e1953..c0304c7 100644
--- a/src/room.h
+++ b/src/room.h
@@ -20,15 +20,14 @@
#include "globals.h"
-#include "obj.h"
-#include "verb.h"
-
/* Our world is an array of rooms, each having a list of objects in
- them, as well as actions that can be performed in the room. Objects
- are added by hooks in rooms, which are provided by the world
- module. */
+ * them, as well as actions that can be performed in them
+ * (verbs). Objects are added by hooks in rooms, which are provided as
+ * function pointers by the world module. */
-typedef struct child_data user_t; // definition of child_data in server.h
+struct child_data;
+struct object_t;
+struct verb_t;
typedef enum room_id { ROOM_NONE = -1 } room_id;
@@ -43,6 +42,7 @@ struct roomdata_t {
char *name;
char *desc;
+ /* unmutable properties, changes will have no effect */
const char * const adjacent[NUM_DIRECTIONS];
void (* const hook_init)(room_id id);
@@ -50,8 +50,12 @@ struct roomdata_t {
/* return values indicate permission to enter/leave,
* setting to NULL defaults to true.
*/
- bool (* const hook_enter)(room_id room, user_t *user);
- bool (* const hook_leave)(room_id room, user_t *user);
+
+ /* NOTE: struct child_data is aliased as "user_t"!!! */
+
+ /* return false to deny entering/leaving a room */
+ bool (* const hook_enter)(room_id room, struct child_data *user);
+ bool (* const hook_leave)(room_id room, struct child_data *user);
void (* const hook_serialize)(room_id room, int fd);
void (* const hook_deserialize)(room_id room, int fd);
void (* const hook_destroy)(room_id room);
diff --git a/src/server.c b/src/server.c
index e42483c..6d90c61 100644
--- a/src/server.c
+++ b/src/server.c
@@ -21,6 +21,7 @@
#include "client.h"
#include "hash.h"
#include "server.h"
+#include "server_reqs.h"
#include "userdb.h"
#include "util.h"
#include "world.h"
@@ -254,10 +255,34 @@ static void new_connection_cb(EV_P_ ev_io *w, int revents)
int readpipe[2]; /* child->parent */
int outpipe [2]; /* parent->child */
+ /* try several methods to create a packet pipe between the child and master: */
+
+ /* first try creating a pipe in "packet mode": see pipe(2) */
if(pipe2(readpipe, O_DIRECT) < 0)
- error("error creating pipe, need linux kernel >= 3.4");
+ {
+ /* then try a SOCK_SEQPACKET socket pair: see unix(7) */
+ if(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, readpipe) < 0)
+ {
+ /* if that failed, try a SOCK_DGRAM socket as a last resort */
+ if(socketpair(AF_UNIX, SOCK_DGRAM, 0, readpipe) < 0)
+ error("couldn't create child-master communication pipe");
+ else
+ debugf("WARNING: Using a SOCK_DGRAM socket pair for IPC, performance may be degraded.\n");
+ }
+ else
+ debugf("Using a SOCK_SEQPACKET socket pair for IPC.\n");
+ }
+ else
+ debugf("Using a packet-mode pipe for IPC.\n");
+
if(pipe2(outpipe, O_DIRECT) < 0)
- error("error creating pipe, need linux kernel >= 3.4");
+ {
+ if(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, outpipe) < 0)
+ {
+ if(socketpair(AF_UNIX, SOCK_DGRAM, 0, outpipe) < 0)
+ error("error creating pipe, need linux kernel >= 3.4");
+ }
+ }
pid_t pid = fork();
if(pid < 0)
@@ -328,6 +353,7 @@ static void init_signals(void)
{
struct sigaction sa;
+ /* SIGINT and SIGTERM cause graceful shutdown */
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_handler = sigint_handler;
@@ -337,6 +363,7 @@ static void init_signals(void)
if(sigaction(SIGTERM, &sa, NULL) < 0)
error("sigaction");
+ /* ignore SIGPIPE */
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -344,7 +371,7 @@ static void init_signals(void)
error("sigaction");
/* libev's default SIGCHLD handler exhibits some really strange
- * behavior, which we don't like, so we use our own ;) */
+ * behavior, which we don't like, so we use our own */
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGCHLD);
sa.sa_handler = sigchld_handler;
@@ -366,7 +393,7 @@ static void parse_args(int argc, char *argv[])
switch(c)
{
case 'h': /* help */
- debugf("FIXME: usage message");
+ debugf("Usage: %s [-d PREFIX] [-a <username> <password>]\n", argv[0]);
exit(0);
case 'a': /* automatic first-run config */
autoconfig = true;
@@ -392,7 +419,7 @@ static SIMP_EQUAL(pid_t, pid_equal);
static void check_libs(void)
{
- debugf("*** Starting NetCosm %s (libev %d.%d, %s) ***\n",
+ debugf("*** NetCosm %s (libev %d.%d, %s) ***\n",
NETCOSM_VERSION, EV_VERSION_MAJOR, EV_VERSION_MINOR, OPENSSL_VERSION_TEXT);
assert(ev_version_major() == EV_VERSION_MAJOR &&
ev_version_minor() >= EV_VERSION_MINOR);
@@ -404,29 +431,32 @@ int server_main(int argc, char *argv[])
parse_args(argc, argv);
- server_socket = server_bind();
-
userdb_init(USERFILE);
+ /* also performs first-time setup: */
check_userfile();
+
load_worldfile();
+ /* initialize request map */
reqmap_init();
/* save some time after a fork() */
client_init();
- /* this initial size very low to make iteration faster */
+ /* this initial size is set very low to make iteration faster */
child_map = hash_init(16, pid_hash, pid_equal);
hash_setfreedata_cb(child_map, free_child_data);
hash_setfreekey_cb(child_map, free);
debugf("Listening on port %d\n", port);
+ server_socket = server_bind();
+
struct ev_loop *loop = ev_default_loop(0);
/* we initialize signals after creating the default event loop
- * because libev grabs SIGCHLD */
+ * because libev grabs SIGCHLD in the process */
init_signals();
ev_io server_watcher;
diff --git a/src/server.h b/src/server.h
index 95e537a..6d3d029 100644
--- a/src/server.h
+++ b/src/server.h
@@ -19,12 +19,9 @@
/* You should use #pragma once everywhere. */
#pragma once
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+#include "globals.h"
-#include "server_reqs.h"
-#include "room.h"
+enum room_id;
/* everything the server needs to manage its children */
struct child_data {
diff --git a/src/server_reqs.c b/src/server_reqs.c
index 4d89ca4..21787f4 100644
--- a/src/server_reqs.c
+++ b/src/server_reqs.c
@@ -21,10 +21,11 @@
#include "hash.h"
#include "multimap.h"
#include "server.h"
+#include "server_reqs.h"
#include "userdb.h"
#include "world.h"
-/* sends a single packet to a child, virtually guarantees receipt */
+/* sends a single packet to a child, mostly reliable */
static void send_packet(struct child_data *child, unsigned char cmd,
const void *data, size_t datalen)
{
@@ -188,7 +189,7 @@ static void req_send_desc(unsigned char *data, size_t datalen, struct child_data
{
strlcat(buf, "There are ", sizeof(buf));
char n[32];
- snprintf(n, sizeof(n), "%lu ", n_objs);
+ snprintf(n, sizeof(n), "%zu ", n_objs);
strlcat(buf, n, sizeof(buf));
strlcat(buf, name, sizeof(buf));
strlcat(buf, "s here.\n", sizeof(buf));
@@ -229,18 +230,33 @@ static void req_set_room(unsigned char *data, size_t datalen, struct child_data
static void req_move_room(unsigned char *data, size_t datalen, struct child_data *sender)
{
(void) data; (void) datalen; (void) sender;
+
+ int status = 0;
+
enum direction_t dir = *((enum direction_t*)data);
struct room_t *current = room_get(sender->room);
- room_user_del(sender->room, sender);
-
- /* TODO: checking */
+ /* TODO: bounds checking on `dir' */
room_id new = current->adjacent[dir];
- int status = 0;
- if(new != ROOM_NONE)
+
+ if(new == ROOM_NONE)
{
- child_set_room(sender, new);
- status = 1;
+ send_msg(sender, "You cannot go that way.\n");
+ }
+ else
+ {
+ struct room_t *new_room = room_get(new);
+
+ if((!new_room->data.hook_enter ||
+ (new_room->data.hook_enter && new_room->data.hook_enter(new, sender))) &&
+ (!current->data.hook_leave ||
+ (current->data.hook_leave && current->data.hook_leave(sender->room, sender))))
+ {
+ room_user_del(sender->room, sender);
+
+ child_set_room(sender, new);
+ status = 1;
+ }
}
send_packet(sender, REQ_MOVE, &status, sizeof(status));
@@ -403,26 +419,8 @@ static void req_inventory(unsigned char *data, size_t datalen, struct child_data
struct object_t *obj = iter->val;
if(!strcmp(name, obj->name))
{
- if(n_objs == 1)
- {
- if(obj->default_article)
- {
- char *article = (is_vowel(name[0])?"An":"A");
- strlcat(buf, article, sizeof(buf));
- strlcat(buf, " ", sizeof(buf));
- }
- strlcat(buf, name, sizeof(buf));
- strlcat(buf, "\n", sizeof(buf));
- }
- else
- {
- char n[32];
- snprintf(n, sizeof(n), "%lu", n_objs);
- strlcat(buf, n, sizeof(buf));
- strlcat(buf, " ", sizeof(buf));
- strlcat(buf, name, sizeof(buf));
- strlcat(buf, "s\n", sizeof(buf));
- }
+ format_noun(buf, sizeof(buf), name, n_objs, obj->default_article, true);
+ strlcat(buf, "\n", sizeof(buf));
send_packet(sender, REQ_BCASTMSG, buf, strlen(buf));
}
diff --git a/src/server_reqs.h b/src/server_reqs.h
index c77c53a..10c1755 100644
--- a/src/server_reqs.h
+++ b/src/server_reqs.h
@@ -20,10 +20,10 @@
#include "globals.h"
-typedef struct child_data user_t;
+#include "server.h"
/* child<->master commands */
-/* children might not implement all of these */
+/* not all of these are implemented by both parties */
/* meanings might be different for the server and child, see comments */
#define REQ_NOP 0 /* server, child: do nothing (used for acknowledgement) */
#define REQ_BCASTMSG 1 /* server: broadcast text; child: print following text */
@@ -32,7 +32,7 @@ typedef struct child_data user_t;
#define REQ_CHANGEUSER 4 /* server: change child login name */
#define REQ_HANG 5 /* <UNIMP> server: loop forever */
#define REQ_KICK 6 /* server: kick PID with message; child: print message, quit */
-#define REQ_WAIT 7 /* server: sleep 10s */
+#define REQ_WAIT 7 /* <DEBUG> server: sleep 10s */
#define REQ_GETROOMDESC 8 /* server: send child room description */
#define REQ_SETROOM 9 /* server: set child room */
#define REQ_MOVE 10 /* server: move child based on direction; child: success or failure */
@@ -50,6 +50,7 @@ typedef struct child_data user_t;
#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 */
/* child states, sent as an int to the master */
#define STATE_INIT 0 /* initial state */
diff --git a/src/userdb.c b/src/userdb.c
index 101b6d0..0a13319 100644
--- a/src/userdb.c
+++ b/src/userdb.c
@@ -19,9 +19,12 @@
#include "globals.h"
#include "client.h"
+#include "client_reqs.h"
#include "hash.h"
#include "multimap.h"
+#include "obj.h"
#include "server.h"
+#include "server_reqs.h"
#include "userdb.h"
static void *map = NULL;
@@ -229,7 +232,7 @@ void userdb_dump(void)
while(iter)
{
struct object_t *obj = iter->val;
- debugf(" - Obj #%lu class %s: name %s\n", obj->id, obj->class->class_name, obj->name);
+ debugf(" - Obj #%"PRI_OBJID" class %s: name %s\n", obj->id, obj->class->class_name, obj->name);
iter = iter->next;
}
}
diff --git a/src/util.c b/src/util.c
index 29a5399..9c4b447 100644
--- a/src/util.c
+++ b/src/util.c
@@ -187,3 +187,93 @@ bool is_vowel(char c)
return false;
}
}
+
+/* $NetBSD: strlcat.c,v 1.4 2005/05/16 06:55:48 lukem Exp $ */
+/* from NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp */
+/* from OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+
+char *format_noun(char *buf, size_t len, const char *name, size_t count, bool default_article, bool capitalize)
+{
+ assert(len > 1);
+ buf[0] = '\0';
+ if(count == 1)
+ {
+ if(default_article)
+ {
+ char *article = capitalize?(is_vowel(name[0])? "An" : "A"):(is_vowel(name[0])? "an" : "a");
+ strlcat(buf, article, len);
+ strlcat(buf, " ", len);
+ strlcat(buf, name, len);
+ }
+ else
+ {
+ char tmp[2];
+ tmp[0] = toupper(name[0]);
+ tmp[1] = '\0';
+ strlcat(buf, tmp, len);
+ strlcat(buf, name + 1, len);
+ }
+ }
+ else
+ {
+ char n[32];
+ snprintf(n, sizeof(n), "%zu", count);
+ strlcat(buf, n, len);
+ strlcat(buf, " ", len);
+ strlcat(buf, name, len);
+ strlcat(buf, "s", len);
+ }
+
+ return buf;
+}
diff --git a/src/util.h b/src/util.h
index 6de6945..8007ec8 100644
--- a/src/util.h
+++ b/src/util.h
@@ -21,6 +21,8 @@
#include "room.h"
+#define WSPACE " \t\r\n"
+
/* 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, ...);
@@ -47,3 +49,8 @@ void write_size(int fd, size_t);
size_t read_size(int fd);
bool is_vowel(char c);
+
+size_t strlcat(char *dst, const char *src, size_t siz);
+
+/* formats a noun's name */
+char *format_noun(char *buf, size_t len, const char *name, size_t count, bool default_article, bool capitalize);
diff --git a/src/verb.h b/src/verb.h
index 348da49..8913962 100644
--- a/src/verb.h
+++ b/src/verb.h
@@ -21,6 +21,7 @@
#include "globals.h"
#include "room.h"
+#include "server.h"
/* the verb API is modeled after that of obj_*, this allows for
* dynamic creation/deletion of verbs, but is also easily
@@ -30,9 +31,6 @@
* callbacks.
*/
-struct child_data;
-typedef struct child_data user_t;
-
struct verb_t;
struct verb_class_t {
const char *class_name;
diff --git a/src/world.c b/src/world.c
index a854e49..8c25d7d 100644
--- a/src/world.c
+++ b/src/world.c
@@ -29,6 +29,9 @@ static struct room_t *world;
static size_t world_sz;
static char *world_name;
+/* map of room names -> rooms */
+static void *world_map = NULL;
+
struct room_t *room_get(room_id id)
{
return world + id;
@@ -124,8 +127,22 @@ void world_save(const char *fname)
world[i].data.hook_serialize(i, fd);
}
+ /* write the object counter so future objects will have sequential ids */
write_uint64(fd, obj_get_idcounter());
+ /* now write the map of room names to ids */
+ void *ptr = world_map, *save;
+ for(unsigned int i = 0; i < world_sz; ++i)
+ {
+ void *key;
+ struct room_t *room = hash_iterate(ptr, &save, &key);
+ if(!room)
+ break;
+ ptr = NULL;
+ write_string(fd, key);
+ write_roomid(fd, &room->id);
+ }
+
close(fd);
}
@@ -230,6 +247,18 @@ bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz
obj_set_idcounter(read_uint64(fd));
+ /* read in the room name -> room map */
+
+ world_map = hash_init(world_sz * 2, hash_djb, compare_strings);
+
+ for(unsigned int i = 0; i < world_sz; ++i)
+ {
+ const char *key = read_string(fd);
+ room_id id = read_roomid(fd);
+ debugf("'%s' -> %d\n", key, id);
+ hash_insert(world_map, key, world + id);
+ }
+
close(fd);
return true;
}
@@ -240,12 +269,12 @@ static SIMP_EQUAL(enum direction_t, dir_equal);
/* loads room data (supplied by the world module) into our internal format */
void world_init(const struct roomdata_t *data, size_t sz, const char *name)
{
- debugf("Loading world with %lu rooms.\n", sz);
+ debugf("Loading world with %zu rooms.\n", sz);
world = calloc(sz, sizeof(struct room_t));
world_sz = 0;
world_name = strdup(name);
- void *map = hash_init(sz / 2 + 1, hash_djb, compare_strings);
+ world_map = hash_init(sz * 2, hash_djb, compare_strings);
for(size_t i = 0; i < sz; ++i)
{
@@ -258,7 +287,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name)
world[i].data.desc = strdup(world[i].data.desc);
debugf("Loading room '%s'\n", world[i].data.uniq_id);
- if(hash_insert(map, world[i].data.uniq_id, world + i))
+ if(hash_insert(world_map, world[i].data.uniq_id, world + i))
error("Duplicate room ID '%s'", world[i].data.uniq_id);
for(int dir = 0; dir < NUM_DIRECTIONS; ++dir)
@@ -266,7 +295,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name)
const char *adjacent_room = world[i].data.adjacent[dir];
if(adjacent_room)
{
- struct room_t *room = hash_lookup(map, adjacent_room);
+ struct room_t *room = hash_lookup(world_map, adjacent_room);
if(room)
world[i].adjacent[dir] = room->id;
}
@@ -284,7 +313,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name)
const char *adjacent_room = world[i].data.adjacent[dir];
if(adjacent_room)
{
- struct room_t *room = hash_lookup(map, adjacent_room);
+ struct room_t *room = hash_lookup(world_map, adjacent_room);
if(room)
world[i].adjacent[dir] = room->id;
else
@@ -339,8 +368,6 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name)
}
hash_free(dir_map);
-
- hash_free(map);
}
static void *verb_map = NULL;
@@ -368,3 +395,12 @@ void *world_verb_map(void)
init_map();
return verb_map;
}
+
+room_id room_get_id(const char *uniq_id)
+{
+ struct room_t *room = hash_lookup(world_map, uniq_id);
+ if(!room)
+ return ROOM_NONE;
+ else
+ return room->id;
+}
diff --git a/src/world.h b/src/world.h
index 5d9ccae..50f9769 100644
--- a/src/world.h
+++ b/src/world.h
@@ -48,14 +48,16 @@ void world_save(const char *fname);
/* loads the world from disk */
bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz, const char *world_name);
-/** verbs **/
+/** global verbs **/
bool world_verb_add(struct verb_t*);
bool world_verb_del(struct verb_t*);
-/* gets the map of verbs */
+/* gets the map of global verbs */
void *world_verb_map(void);
void world_free(void);
/* this goes in world_ and not room_ */
struct room_t *room_get(room_id id);
+
+room_id room_get_id(const char *uniq_id);