//
// File: flock.c
// Creator: Robocoder@TMI-2
//
// Revision history:
// 06-22-93 - robo - created
// 06-25-93 - robo - optimizations; remove_stale_locks() no longer needed
// 06-25-93 - robo - replaced array deletion code with exclude_array()
// ??-??-93 - ???? - added DEBUG_FLOCK code to track down a suspected bug
// 08-25-93 - robo - kludge to reuse an unfreed editor lock
// - removed DEBUG_FLOCK code
// 04-09-94 - robo - kludge to identify ftp user
//
// File locking mechanism/server
// Based on code in /cmds/file/_ed.c by Buddha.
#include <config.h>
#include <mudlib.h>
#include <flock.h>
#include <logs.h>
#include <net/daemons.h>
#include <commands.h>
#include <uid.h>
#include <driver/origin.h>
inherit SERVER;
// Uses 2 arrays rather than a mapping for the ease in searching for
// either the file or the lockobj (player/object that locked the file).
// It's difficult to keep the lists accurate, but not impossible to handle:
// o multiple logins of the same character
// o one object may have many locks
// o stale locks from link-deaths
string *files, *lockobjs;
// A special mapping tracks a file's timestamps and previous object
// for logging and resource tracking (see below).
mapping filestats;
// To enable logging:
// #define FILE_LOG in $(MUDLIB)/include/logs.h
// For more selective logging, #define NON_CRITICAL_PATHS
// in $(MUDLIB)/include/flock.h.
// This should be a list of paths that _should not_ be logged.
// For example,
// #define NON_CRITICAL_PATHS "/tmp/","/open/","/ftp/"
// Note: the leading and trailing slashes, the quotes around each top level
// path, and the commas for delimeters.
// prototypes for local functions
void log_change(string pathname, string flag);
string query_file_change(string pathname);
int get_timestamp(string pathname);
#define PUBLIC
#define PRIVATE static
PUBLIC void create() {
seteuid(ROOT_UID);
files = ({ });
lockobjs = ({ });
filestats = ([ ]);
}
/*
* flock()
* If mode == F_LOCK, attempt to gain a file lock.
* If mode == F_UNLOCK, attempt to remove a file lock.
* Returns 1 if successful, 0 otherwise.
*/
PUBLIC int flock(string pathname, int mode) {
int position;
object tobj, locker;
// sanity checks
// if the arrays are off, then reset them,
// and pray nothing [else] goes wrong...
if (sizeof(lockobjs) != sizeof(files)) create();
// reject bad pathnames, i.e. null or a null string
if (!pathname || pathname == "") return 0;
locker = this_player();
if (!this_player() || !geteuid(this_player()))
locker = previous_object();
if (mode == F_LOCK) {
position = member_array(pathname, files);
if (position != -1) {
// sanity check
// file is already locked...let's see if the lock is stale
#ifdef FILE_LOG
if (!lockobjs[position]) {
log_change(pathname, "Uh-Oh");
return 0;
}
#endif
// who own's the lock, and is he/she still online?
tobj = find_object(lockobjs[position]);
if (tobj && tobj->query_linkdead()) {
// Ok, just re-assign lock
lockobjs[position] = file_name(locker);
return 1;
} else {
#ifdef REUSE_ED_LOCK_KLUDGE
// Allow a user to reuse an editor lock under special conditions
if (locker == tobj && files[position] == pathname
&& file_name(previous_object()) == CMD_ED
&& filestats[pathname]["prevobj"] == CMD_ED
&& !in_edit(locker)) {
return 1;
}
#endif
// File busy
return 0;
}
}
lockobjs += ({ file_name(locker) });
files += ({ pathname });
filestats[pathname] =
([ "timestamp" : get_timestamp(pathname),
"prevobj" : file_name(previous_object()) ]);
} else if (mode == F_UNLOCK) {
position = member_array(pathname, files);
if (position == -1) {
return 0;
}
// check owner of lock
if (this_player()
&& file_name(locker) != lockobjs[position]
&& !adminp(geteuid(locker))) {
#ifdef FILE_LOG
// Hmm...naughty, naughty...
log_change(pathname, "***");
#endif /* FILE_LOG */
return 0;
}
// remove lock
#ifdef FILE_LOG
log_change(pathname, query_file_change(pathname));
#endif /* FILE_LOG */
filestats[pathname] = ([ ]);
map_delete(filestats, pathname);
files -= ({ pathname });
lockobjs = exclude_array(lockobjs, position);
} else {
// unrecognized mode
return 0;
}
return 1;
}
/*
* free_lockobj()
* Frees all locks associated with a player.
* Note: this function is called by /std/user.c
*/
PUBLIC void free_lockobj(object player) {
int position;
string tstr;
string pathname;
//if(base_name(previous_object()) != USER_OB ) return; // Leto
tstr = file_name(player);
while ((position = member_array(tstr, lockobjs)) != -1) {
pathname = files[position];
if (in_edit(player)
&& filestats[pathname]["prevobj"] == CMD_ED) {
call_other(CMD_ED, "done_editing");
continue;
}
#ifdef FILE_LOG
log_change(pathname, query_file_change(pathname));
#endif /* FILE_LOG */
filestats[pathname] = ([ ]);
map_delete(filestats, pathname);
files -= ({ files[position] });
lockobjs = exclude_array(lockobjs, position);
}
}
/*
* query_lockobj()
* Returns the object that owns that lock on "file"
*/
PUBLIC object query_lockobj(string file) {
int i;
if (!file || file == "") return 0;
i = member_array(file, files);
if (i != -1) {
return find_object(lockobjs[i]);
}
return 0;
}
/*
* query_lockfn()
* Returns the name of _a_ file associated with the locking object.
*/
PUBLIC string query_lockfn(object locker) {
int i, j, k;
string s, t;
if (!locker) return 0;
s = file_name(locker);
j = sizeof(lockobjs);
k = member_array(s, lockobjs);
if (k != -1) {
for (i = k; i < j; i++) {
t = files[i];
if (s == lockobjs[i]
&& filestats[t]["prevobj"] == file_name(previous_object()))
return t;
}
}
return 0;
}
/*
* get_timestamp()
* Returns the timestamp of a file, or 0 if the file doesn't exist.
*/
PRIVATE int get_timestamp(string pathname) {
mixed *stats;
int position;
int stamp;
if (file_size(pathname) >= 0) {
stats = stat(pathname);
stamp = stats[1];
} else
stamp = 0;
return stamp;
}
/*
* query_file_change()
* Returns a string that identifies the manner by which a file was changed.
*/
PRIVATE string query_file_change(string pathname) {
mixed *stats;
int position;
int oldstamp, newstamp;
if ((position = member_array(pathname, files)) == -1)
return 0;
newstamp = get_timestamp(pathname);
oldstamp = filestats[pathname]["timestamp"];
if (oldstamp != newstamp) {
if (oldstamp) {
if (newstamp)
return "!"; // changed
else
return "-"; // purged
} else
return "+"; // added
}
// else file not changed or not created
return 0;
}
#ifdef FILE_LOG
/*
* log_change()
* Logs file changes to the log file $(FILE_LOG).
*/
PUBLIC void log_change(string pathname, string flag) {
string s;
#ifdef NON_CRITICAL_PATHS
string *paths;
int i, k, x;
if (!(previous_object() == MASTER_OB || origin() == ORIGIN_LOCAL))
return;
if (!flag || flag == "")
return;
paths = ({ NON_CRITICAL_PATHS });
if (this_player()) {
paths += ({ user_path(geteuid(this_player())) });
} else if (previous_object() == find_object(FTP_D)) {
paths += ({ user_path(geteuid(previous_object())) });
}
x = sizeof(paths);
for (i = 0; i < x; i++) {
s = paths[i];
k = strlen(paths[i]);
if (k <= strlen(pathname) && s == pathname[0..k-1]) {
// don't log this one
return;
}
}
#else
if (!(previous_object() == MASTER_OB || origin() == ORIGIN_LOCAL))
return;
if (!flag || flag == "")
return;
#endif /* !NON_CRITICAL_PATHS */
if (this_player())
s = capitalize(geteuid(this_player()));
else {
s = file_name(previous_object());
// ftpd kludge...assuming ftpd does a seteuid()
if (s == FTP_D) {
s = "[ftp] " + capitalize(geteuid(previous_object()));
}
}
log_file(FILE_LOG, wrap(s + " " + flag + " \"" + pathname +
"\" [" + extract(ctime(time()), 4, 15) + "]"));
}
#endif /* FILE_LOG */
/*
* dump_locks()
* A debugging function that prints out a list of outstanding locks--
* filename, lock owner, timestamp of file.
*/
PUBLIC void dump_locks() {
int i, j, k;
string tstr, tstr2;
object tobj;
i = sizeof(lockobjs);
j = sizeof(files);
k = sizeof(filestats);
if (i != j) {
if (i < j)
j = i;
write("Hmm...array inconsistency.\n");
}
if (i != k) {
write("Hmm...mapping inconsistency.\n");
}
for (i = 0; i < j; i++) {
if (lockobjs[i]) {
tobj = find_object(lockobjs[i]);
if (tobj) {
tstr = geteuid(tobj);
if (!tstr)
tstr = "(None)";
tstr += " (" + lockobjs[i] + ")";
} else
tstr = lockobjs[i];
tstr2 = files[i];
if (k = filestats[tstr2]["timestamp"])
printf("%20s\t%s\t%s\n", tstr, tstr2, ctime(k));
else
printf("%20s\t%s\t[New File]\n", tstr, tstr2);
} else {
write("Hmm...invalid entries.\n");
}
}
}