pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* 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);
}