/* lock.c */
/*
* This is the core of Ralph Melton's rewrite of the @lock system.
* These are some of my underlying assumptions:
*
* 1) Locks are checked many more times than they are set, so it is
* quite worthwhile to spend time when setting locks if it expedites
* checking locks later.
*
* 2) Most possible locks are never used. For example, in the days
* when there were only basic locks, use locks, and enter locks, in
* one database of 15000 objects, there were only about 3500 basic
* locks, 400 enter locks, and 400 use locks.
* Therefore, it is important to make the case where no lock is present
* efficient both in time and in memory.
*
* 3) It is far more common to have the server itself check for locks
* than for people to check for locks in MUSHcode. Therefore, it is
* reasonable to incur a minor slowdown for checking locks in MUSHcode
* in order to speed up the server's checking.
*/
#include "copyrite.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#include "conf.h"
#include "mushdb.h"
#include "attrib.h"
#include "externs.h"
#include "lock.h"
#include "match.h"
#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#include "mymalloc.h"
#include "confmagic.h"
/* If any lock_type ever contains the character '|', reading in locks
* from the db will break.
*/
const lock_type Basic_Lock = "Basic";
const lock_type Enter_Lock = "Enter";
const lock_type Use_Lock = "Use";
const lock_type Zone_Lock = "Zone";
const lock_type Page_Lock = "Page";
const lock_type Tport_Lock = "Teleport";
const lock_type Speech_Lock = "Speech";
const lock_type Listen_Lock = "Listen";
const lock_type Parent_Lock = "Parent";
const lock_type Link_Lock = "Link";
const lock_type Leave_Lock = "Leave";
const lock_type Drop_Lock = "Drop";
const lock_type Give_Lock = "Give";
const lock_type Mail_Lock = "Mail";
/* Define new lock types here. */
const char *lock_types[] =
{
"Basic",
"Enter",
"Use",
"Zone",
"Page",
"Teleport",
#ifdef SPEECH_LOCK
"Speech",
#endif
#ifdef LISTEN_LOCK
"Listen",
#endif
"Parent",
"Link",
#ifdef LEAVE_LOCK
"Leave",
#endif
#ifdef DROP_LOCK
"Drop",
#endif
#ifdef GIVE_LOCK
"Give",
#endif
"Mail",
/* Add new lock types just before this line. */
NULL
};
struct boolexp *getlock _((dbref thing, lock_type type));
lock_type match_lock _((lock_type type));
void add_lock _((dbref thing, lock_type type, struct boolexp * key));
static void free_one_lock_list _((lock_list *ll));
void delete_lock _((dbref thing, lock_type type));
void free_locks _((lock_list *ll));
static lock_type check_lock_type _((dbref player, dbref thing, char *name));
void do_unlock _((dbref player, const char *name, lock_type type));
void do_lock _((dbref player, const char *name, const char *keyname, lock_type type));
int eval_lock _((dbref player, dbref thing, lock_type ltype));
/* Given a lock type, find a lock.
* This depends on the implementation of lock_type as a string.
*/
struct boolexp *
getlock(thing, type)
dbref thing;
lock_type type;
{
struct lock_list *ll;
if (!GoodObject(thing))
return TRUE_BOOLEXP;
ll = Locks(thing);
while (ll && ll->type) {
if (strcasecmp(ll->type, type) == 0) {
return (ll->key);
}
ll = ll->next;
}
return TRUE_BOOLEXP;
}
/* Is this lock in our set of canonical locks? */
lock_type
match_lock(type)
lock_type type;
{
int i;
for (i = 0; lock_types[i] != NULL; i++) {
if (strcasecmp(lock_types[i], type) == 0) {
return lock_types[i];
}
}
return NULL;
}
/* Set the lock type on thing to boolexp.
* This is a primitive routine, to be called by other routines.
* It will go somewhat wonky if given a NULL boolexp.
* It will allocate memory if called with a string that is not already
* in the lock table.
*/
void
add_lock(thing, type, key)
dbref thing;
lock_type type;
struct boolexp *key;
{
struct lock_list *ll;
lock_type real_type = type;
if (!GoodObject(thing)) {
return;
}
ll = Locks(thing);
while (ll && (strcasecmp(ll->type, type) != 0)) {
ll = ll->next;
}
if (ll) {
/* We're replacing an existing lock. */
free_boolexp(ll->key);
ll->key = key;
} else {
ll = (lock_list *) mush_malloc(sizeof(lock_list), "lock_list");
if (!ll) {
/* Oh, this sucks */
do_log(LT_ERR, 0, 0, "Unable to malloc memory for lock_list!");
} else {
real_type = match_lock(type);
if (real_type == NULL) {
real_type = strdup(type);
#ifdef MEM_CHECK
add_check("lock_type");
#endif
}
ll->type = real_type;
ll->key = key;
ll->next = Locks(thing);
Locks(thing) = ll;
}
}
}
/* Very primitive. */
static void
free_one_lock_list(ll)
lock_list *ll;
{
int i;
if (ll == NULL)
return;
free_boolexp(ll->key);
/* A quandary: How do we tell whether to free the type string?
* If it's in our string table, we want not to free it, but otherwise,
* we do.
* Answer: since this is called infrequently, we can just check again
* to see whether the string is in the table of locks.
*/
for (i = 0; lock_types[i] != NULL; i++) {
/* n.b. We use ==, not strcasecmp. If it's a duplicate, we want
* to free it.
*/
if (ll->type == lock_types[i]) {
break;
}
}
if (lock_types[i] == NULL) { /* We didn't find it in the table. */
mush_free((Malloc_t) ll->type, "lock_type");
}
mush_free((Malloc_t) ll, "lock_list");
}
/* Another primitive routine. */
void
delete_lock(thing, type)
dbref thing;
lock_type type;
{
struct lock_list *ll, **llp;
if (!GoodObject(thing)) {
return;
}
llp = &(Locks(thing));
while (*llp && strcasecmp((*llp)->type, type) != 0) {
llp = &((*llp)->next);
}
if (*llp != NULL) {
ll = *llp;
*llp = ll->next;
free_one_lock_list(ll);
}
}
void
free_locks(ll)
lock_list *ll;
{
lock_list *ll2;
while (ll) {
ll2 = ll->next;
free_one_lock_list(ll);
ll = ll2;
}
}
/* Check to see that the lock type is a valid type.
* If it's not in our lock table, it's not valid,
* unless it begins with 'user:' or an abbreviation thereof,
* in which case the lock type is the part after the :.
* As an extra check, we don't allow '|' in lock names because it
* will confuse our db-reading routines.
*
* Might destructively modify name.
*/
static lock_type
check_lock_type(player, thing, name)
dbref player;
dbref thing;
char *name;
{
lock_type ll;
char *sp;
/* Special-case for basic locks. */
if (!name || !*name)
return Basic_Lock;
/* Normal locks. */
ll = match_lock(name);
if (ll != NULL)
return ll;
/* If the lock is set, it's allowed, whether it exists normally or not. */
if (getlock(thing, name) != TRUE_BOOLEXP) {
return name;
}
/* Check to see if it's a well-formed user-defined lock. */
sp = strchr(name, ':');
if (!sp) {
notify(player, "Unknown lock type.");
return NULL;
}
*sp++ = '\0';
if (!string_prefix("User", name)) {
notify(player, "Unknown lock type.");
return NULL;
}
if (index(sp, '|')) {
notify(player, "The character \'|\' may not be used in lock names.");
return NULL;
}
return sp;
}
void
do_unlock(player, name, type)
dbref player;
const char *name;
lock_type type;
{
dbref thing;
char *sp;
lock_type real_type;
/* check for '@unlock <object>/<atr>' */
sp = (char *) index(name, '/');
if (sp) {
do_atrlock(player, name, "off");
return;
}
if ((thing = match_controlled(player, name)) != NOTHING) {
if ((real_type = check_lock_type(player, thing, (char *) type)) != NULL) {
delete_lock(thing, real_type);
notify(player, "Unlocked.");
}
}
}
void
do_lock(player, name, keyname, type)
dbref player;
const char *name;
const char *keyname;
lock_type type;
{
lock_type real_type;
dbref thing;
struct boolexp *key;
char *sp;
/* check for '@lock <object>/<atr>' */
sp = (char *) index(name, '/');
if (sp) {
do_atrlock(player, name, "on");
return;
}
if (!keyname || !*keyname) {
do_unlock(player, name, type);
return;
}
switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
case NOTHING:
notify(player, "I don't see what you want to lock!");
return;
case AMBIGUOUS:
notify(player, "I don't know which one you want to lock!");
return;
default:
if (!controls(player, thing)) {
notify(player, "You can't lock that!");
return;
}
if (Destroyed(thing)) {
notify(player, "Why would you want to lock garbage?");
return;
}
break;
}
key = parse_boolexp(player, keyname);
/* do the lock */
if (key == TRUE_BOOLEXP) {
notify(player, "I don't understand that key.");
} else {
if ((real_type = check_lock_type(player, thing, (char *) type)) != NULL) {
/* everything ok, do it */
add_lock(thing, real_type, key);
notify(player, "Locked.");
}
}
}
/* Evaluate lock ltype on thing for player */
int
eval_lock(player, thing, ltype)
dbref player;
dbref thing;
lock_type ltype;
{
return eval_boolexp(player, getlock(thing, ltype), thing, 0, ltype);
}