/* File: session.c * Author: Jean Thierry-Mieg (mieg@mrc-lmb.cam.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1992 * ------------------------------------------------------------------- * Acedb 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 2 * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and * Jean Thierry-Mieg (CRBM du CNR, France) mieg@kaa.cnrs-mop.fr * * Description: * Session control, passwords, write access, locks etc. * Exported functions: * HISTORY: * Last edited: Jul 29 10:55 2003 (edgrif) * * Jun 20 10:54 2001 (edgrif): Add extra check of blockN.wrm file read/write * access to check for write access permission. * * Jul 20 14:46 2000 (edgrif): Change logfile format for intial record for * new user, add standard prefix of time, network_id and PID. * * Dec 16 11:23 1999 (edgrif): Added callbacks for server, made models.wrm * test fatal if models.wrm not found. * * Sep 29 11:30 1999 (edgrif): Move sync write & nolock options from passwd.wrm * to database.wrm, required moving getOtherDatabaseConfigs() call. * * Sep 21 11:54 1999 (edgrif): Added code to read/set sync write option * from passwd.wrm. * * Apr 19 12:06 1999 (fw): added // for messages written to stdout * * Apr 19 12:06 1999 (fw): set the readlock for (thisSession_num - 1) * * Jan 25 16:01 1999 (edgrif): Remove getLogin, now in w1/utils.c * * Dec 3 14:43 1998 (edgrif): Change calls to new interface to aceversion. * * Nov 26 11:21 1998 (fw): removed waction stuff * * Oct 22 11:54 1998 (edgrif): Add action.h for init/close_action() * THERE IS A PROBLEM WITH CLASHES WITH disk.h for BP..sigh. * * Oct 21 14:01 1998 (edgrif): Remove use of isGraphics to make graphOut * do a printf, not needed now. * * Sep 11 09:56 1998 (rbsk) need strnew for filGetFullPath assigned to ACEDIR * * Sep 11 09:56 1998 (edgrif): Replace calls to messcrash with messExit * where the problem is a user error. * * Sep 11 09:43 1998 (edgrif): Replace messcrash calls with messExit * where this is appropriate. Remove from messages any mention * of "setenv ACEDB" as this is now deprecated. * * Sep 3 11:14 1998 (edgrif): Remove "ACEDB" from fprintf string in * acedbCrash, this is provided by uMessCrash now. * * Aug 21 14:49 1998 (rd): moved ruid/euid back here, along with getLogin * and dump/crash handlers * * missing date/name * - Moved the write access messages to the locking subroutines to cover * - all uses. And removed time etc which is now automatically done. * * Sep 5 10:44 1997 (rd) * - WIN32 runs sessionFilName() like UNIX when defined(NON_GRAPHIC) * WinTace user should specify ACEDB directory on the commandline? * * Jun 9 14:50 1996 (rd) * - extern uid_t euid, ruid moved to session.h * * Jun 6 17:41 1996 (rd) * * Jun 3 19:00 1996 (rd) * * Feb 2 11:46 1992 (mieg): interactive session control * * Jan 24 11:20 1992 (mieg): session fusion. * Created: Fri Jan 24 11:19:27 1992 (mieg) * CVS info: $Id: session.c,v 1.265 2004/04/09 22:19:09 mieg Exp $ *------------------------------------------------------------------- */ /* $Id: session.c,v 1.265 2004/04/09 22:19:09 mieg Exp $ */ /* #define CHRONO define it to get global chrono on whole session */ #define DEF_BP /* avoid typedefing it as void* in disk.h */ typedef struct block *BLOCKP ; typedef BLOCKP BP ; #include #if defined(__CYGWIN__) #include #endif #include #include /* for autosave */ #include #include #include #include /* for freeOutInit */ #include #include #include /* for calls in swapSuperBlock() */ #include #include #include #include #include #include /* defines BLOCKHEADER */ #include /* uses BLOCKHEADER */ #include /* lexSessionStart etc.. */ #include #include /* prefReadDBPrefs() */ #include /* for readModels() */ #include /* for tStatus */ #include /* for helpSetDir during initilisation */ #include #include #include /* Stub functions for user ID's in non-multiuser systems Note: A WIN32 implementation for Windows NT may be possible */ #if defined(MACINTOSH) void seteuid(uid_t uid) { return ; } uid_t getuid() { return 1 ; } uid_t geteuid() { return 1 ; } int gethostname(char *b, int l) { *b = 0; } typedef int pid_t; pid_t getpid (void) { return 0; } #endif /* not MAC */ #if defined(FreeBSD) || defined(DARWIN) #define O_SYNC O_FSYNC /* O_SYNC may be XOPEN thing +gg */ #endif /* FreeBSD */ static void closeSession(BOOL doSave) ; /* One day this should hold all of the below globals....and should be passed into * session calls as context. */ static SessionContext session_context_G = NULL ; /************************************************************/ /* globals declared in session.h */ SESSION thisSession ; BOOL swapData = FALSE ; VoidRoutine messcrashroutine = simpleCrashRoutine; /* The meaning of this function has changed now. it is now only used for acedb code, where acedbCrash() is messCrashRegister'd with the message package. keep using this name for now because used frequently in code */ /* By default (simpleCrashRoutine) will be close down in style, but not save the database. A user defined routine may check write access and save if required.. (Should always release locks). */ /************************************************************/ /* local globals */ /* A section of database configuration variables that can be overridden from */ /* wspec/database.wrm */ /* number of sessions to keep living before a session expires (if non-permanent and not readlocked) Default is 2, must be > 1 */ enum {SESSKEEPALIVE_MIN = 2, SESSKEEPALIVE_DEF = 2} ; static int sessionKeepAlive = SESSKEEPALIVE_DEF ; /* init'd in sessionInit */ enum {RDLCKTIMEOUT_MIN = 1, RDLCKTIMEOUT_DEF = 8} ; static int readlockTimeout = RDLCKTIMEOUT_DEF ; /* readlock files older than that * are removed and ignored */ /* suppressLock - indicates whether the file-locking system lockf(3) * should be avoided or not. * The default depends on OS type, but it can be * set per-database by inserting the word NOLOCK * in wspec/database.wrm */ #if defined(MACINTOSH) || defined(__CYGWIN__) || defined(DARWIN) #define F_ULOCK 0 #define F_TLOCK 0 static BOOL suppressLock = TRUE ; /* don't use the lockf system, This fine on win & mac even without NOLOCK for this database, because it is unlikely for a Unix program (which uses lockf) to access a database on a PC or Mac */ /* stub.. */ BOOL lockf ( int lockFD, int mode1, int mode2 ) { return 0 ; } #else /* UNIX */ static BOOL suppressLock = FALSE ; /* do use lockf */ #endif /* not MAC */ /* If TRUE then all acedb writes to the database will be with O_SYNC which */ /* guarantees the data has been written to disk before returning, BUT is */ /* VERY slow. (n.b. this need not be a global static but is placed here so */ /* all the database configuration variables are together.) */ static BOOL syncWrite = FALSE ; /* If TRUE then we stop old, non-object-timestamp-aware code from accessing the database. This ensures that an object cannot be updated without its timestamp being updated also. This is a hack, we should really increment the major release number, but it is only required for a small number of databases, so we don't want to obsolete all of them. */ static BOOL enforceTimestamps = FALSE; /* The string to use as the "name" of this session user. */ static char *userSessionUser = NULL ; /* This is a title that can be given to the database and will override that */ /* given in wspec/displays.wrm for the main window. Really the main window */ /* title should be disabled. */ static char *db_title_G = NULL ; /* If this is TRUE then any users who are in the unix group that owns the */ /* wspec/models.wrm will be allowed to initialise the database. Uncomment */ /* the GROUP_ADMIN option in wspec/database.wrm to activate this. */ static BOOL group_admin_G = FALSE ; /* end of database configuration variables. */ /* some write access and save behaviour controlling variables, they may * need rationalising..... */ static BOOL dropWriteAccess = FALSE ; /* once TRUE, write access is forbidden and can't be regained this is an odd behaviour */ static int saveMagic = 0 ; /* read/write verification seems always to be zero currently ??? */ static BOOL writeAccess = FALSE ; /* Does process have write access currently ? */ static BOOL saveDB_G = TRUE ; /* Even if the process can get write access this flag if FALSE prevents any saving. */ static BLOCK theSuperBlock ; static BOOL debug = FALSE; /* more log-file output */ static char *messcrashbuf; /* a memory buffer that is allocated at initialization time and free'd when acedb crashes, to gain some extra memory for the crash recovery procedures */ /* Logfile state, sometimes we run without being able to log, this is */ /* allowed for databases where we don't have write permission in the */ /* database directory. */ static ACEOUT logfile_out = NULL; static BOOL logfile_possible = FALSE; static BOOL logfile_request_new_G = FALSE ; /* graphical versions of acedb may need to have special routines called at */ /* various points during session handling. */ static GraphSessionCB graphCBs_G = NULL ; /* Boolean forceInit_G indicates, if TRUE, that the database ** is to be initialised without further prompting */ static BOOL forceInit_G = FALSE; /************************************************************/ /* The server needs to perform some special actions on initialisation/ */ /* termination and does so through callbacks. */ static ServerSessionCB serverCBs_G = NULL ; /************************************************************/ /* if set, externalServer will be called once on every new object, and always in query(0,..) and in grep Registration of this pointer done via xCientInit */ KEYSET (*externalServer) (KEY key, char *query, char *grep, BOOL getNeighbours) ; void (*externalSaver) (KEY key, int action) ; void (*externalAlias) (KEY key, char *oldname, BOOL keepOldName) ; void (*externalCommit) (void) ; BOOL (*externalServerState) (int nn) ; char* (*externalServerName) (void) ; /************************************************************/ static void sessionDoInit (void) ; /* session.c's own initialisation */ static char *getConfiguredDatabaseName (void) ; static void getOtherDatabaseConfigs(int *keepAlive, int *timeOut, BOOL *suppressLock, BOOL *syncWrite, BOOL *enforceTimestamps, char **db_title, BOOL *group_admin) ; static int sessionFindSon (Array sessionTree, KEY father, KEY *sonp) ; static BOOL sessionDatabaseWritable (BOOL doTalk); static BOOL blocksWriteable(BOOL report_errors) ; static BOOL sessionCheckUserName (BOOL doTalk); static BOOL readlockCreateDir (void); static BOOL readlockCreateFile (void); static BOOL readlockUpdateFile (void); /* increase thisSession.session before calling */ static Array readlockGetSessionNumbers (BOOL isReport); static void acedbLog (char *text, void *user_pointer); static void acedbCloseLog(aceLogExitType exit_type) ; static BOOL startNewLogFile(BOOL just_reopen, char *old_log_name, char *log_msg, BOOL new_session) ; static void acedbExit (char *mesg_buf, void *user_pointer); static void acedbCrash (char *mesg_buf, void *user_pointer); static BOOL checkFileGroupPerm(struct stat *passwd_stat) ; /************************************************************/ static VoidRoutine writeAccessChangeRoutine = 0; /************************************************************/ /* this mechanism is used to register a function that redraws the window layout or changes menus etc. when the write access changes */ VoidRoutine writeAccessChangeRegister (VoidRoutine func) /* call with NULL to enquire function */ { VoidRoutine old = writeAccessChangeRoutine ; if (func) writeAccessChangeRoutine = func ; return old ; } /* writeAccessChangeRegister */ void writeAccessChangeUnRegister (void) { writeAccessChangeRoutine = NULL ; return; } /* writeAccessChangeUnRegister */ /*************************************************************/ /* this mechanism is used to reflect changes in the session structure in a graphical representation of session objects */ static VoidRoutine sessionChangeRoutine = 0; VoidRoutine sessionChangeRegister (VoidRoutine func) { VoidRoutine old = sessionChangeRoutine ; sessionChangeRoutine = func ; return old ; } /* sessionChangeRegister */ /*************************************************************/ #ifndef ACEDB5 #ifdef ACEDB4 void swapSuperBlock(BLOCKP bp) /* used also by disknew.c */ { bp->h.disk = swapDISK(bp->h.disk); bp->h.nextdisk = swapDISK(bp->h.nextdisk); bp->h.session = swapInt(bp->h.session); bp->h.key = swapKEY(bp->h.key); bp->gAddress = swapDISK(bp->gAddress); bp->mainRelease = swapInt(bp->mainRelease); bp->subDataRelease = swapInt(bp->subDataRelease); bp->subCodeRelease = swapInt(bp->subCodeRelease); bp->alternateMainRelease = swapInt(bp->alternateMainRelease); } /* swapSuperBlock */ #endif #endif /*************************************************************/ /* user-defined routine for messExit - * perform acedb-specific tasks when we have to exit * because of some error. * NOTE, that this exit is not as severe as acedbCrash * it merely means that acedb is unable to continue. */ static void acedbExit (char *mesg_buf, void *user_pointer) { /* make copy of message to prevent mesg_buf tangle */ char *text_copy = strnew(mesg_buf, 0); /* output the exit-message to the user * uuugggghhh, unfortunately this call to messout subverts the principle * that exit messages should _not_ go to the scrolled message window. * So before doing them I make sure we switch back to popup windows. In * the giface version this call does nothing. */ if (graphCBs_G) graphCBs_G->exit_msg_cb(FALSE) ; messout(text_copy); /* See server code below. */ messdump (text_copy); /* the messcrashroutine should call aceQuit, to clean up, it may give the user a chance to save their work so far */ if (messcrashroutine) { VoidRoutine the_routine = messcrashroutine ; messcrashroutine = 0; /* avoids blind recursion */ (*the_routine)(); /* call user cleanup routine */ } acedbCloseLog(ACELOG_ABNORMAL_EXIT) ; /* Give the server a chance to clean up/set lock files before we exit. * Note that we do not pass the exit message as the second parameter because * the messout() above has already dumped the message, if you remove the messout() * above you should pass the message in here instead. */ if (serverCBs_G) serverCBs_G->exit.funcs.mesgfunc(serverCBs_G->exit.data, NULL) ; messfree(text_copy) ; return; /* give control back to messExit to * terminate the program */ } /* acedbExit */ /************************************************************/ static void acedbCrash (char *mesg_buf, void *user_pointer) /* user-defined routine for messcrash - * perform acedb-specific tasks when we have to crash out * in a ball of flames because of a FATAL ERROR. * This is traditionally used to crash out for failed * assertions, as the crash location in the code will be reported. */ { char *text_copy; if (messcrashbuf) free (messcrashbuf) ; /* frees up space that might be useful */ /* copy message-text for messout to avoid mesg_buf tangle */ /* NOTE, don't use messalloc & Co, to avoid using more code * that could potentially fail */ text_copy = malloc(strlen(mesg_buf) + 1); if (!text_copy) { /* we're in big trouble */ fprintf (stderr, "fatal memory allocation error in acedbCrash() termination routine, abort"); /* Hopefully will write a core file. */ abort() ; } strcpy (text_copy, mesg_buf); /***** output crash-message to user and to log-file ****/ /* uuugggghhh, unfortunately these calls to messout subvert the principle */ /* that crash messages should _not_ go to the scrolled message window. */ /* So before doing them I make sure we switch back to popup windows. In */ /* the giface version this call does nothing. */ if (graphCBs_G) graphCBs_G->exit_msg_cb(FALSE) ; messout (text_copy); messout ("Sorry for the crash, please report it, " "if it is reproducible\n") ; messdump (text_copy); /********* if memory failure report usage info **************/ if (strstr (text_copy, HANDLE_ALLOC_FAILED_STR) != NULL) { if (logfile_out) { tStatus (logfile_out); aceOutFlush (logfile_out); } else /* e.g. if can't write database/ directory */ { ACEOUT status_out = aceOutCreateToStderr (0); tStatus (status_out) ; aceOutDestroy (status_out); } } /* the messcrashroutine should call aceQuit, to clean up, it may give the user a chance to save their work so far */ if (messcrashroutine) { VoidRoutine the_routine = messcrashroutine ; messcrashroutine = 0; /* avoids blind recursion */ (*the_routine)(); /* call user cleanup routine */ } acedbCloseLog(ACELOG_CRASH_EXIT) ; /* Give the server a chance to clean up/set lock files before we exit. */ if (serverCBs_G) serverCBs_G->crash.funcs.mesgfunc(serverCBs_G->crash.data, text_copy) ; free(text_copy) ; return; } /* acedbCrash */ /************************************************************/ /* */ /* acedb log file routines, used for the general acedb log file (log.wrm). */ /* */ /* Start a new log file at request of user, probably because old one is */ /* getting too big. */ /* if log_file_name is NULL then old log is called "log..wrm" */ /* */ BOOL sessionNewLogFile(char *old_log_name) { BOOL result = FALSE ; result = startNewLogFile(FALSE, old_log_name, NULL, FALSE) ; return result ; } /* Request that new log file be started next time a logging entry is made, */ /* this routine can be called from a signal handler so that an ace server */ /* for instance can be sent a signal to start a new log. */ /* */ void sessionRequestNewLogFile(void) { logfile_request_new_G = TRUE ; return ; } /* Do a straight close/reopen of the logfile, can be used in conjunction */ /* with the "rotatelogs" script found on some unix versions (e.g. linux). */ /* */ BOOL sessionReopenLogFile(void) { BOOL result = FALSE ; result = startNewLogFile(TRUE, NULL, NULL, FALSE) ; return result ; } /* Rename the existing log file and start a new one. Gets called either when */ /* requested by user or when database is bootstrapped (start new log because */ /* old one is really only applicable to previous incarnation of database. */ /* */ /************************************************************* * The messages in logfiles only really apply to the database * in its current state, if the database is being rebuilt the * previous log becomes inappropriate. * The previous log is therefore moved to a backup location, * and a new logfile is started at this point (SANgc02773) */ /* if log_file_name is NULL then old log is called "log..wrm" */ /* if log_msg is non-NULL then its written to end of old log. */ /* */ static BOOL startNewLogFile(BOOL just_reopen, char *old_log_name, char *log_msg, BOOL new_session) { BOOL result = FALSE ; if (logfile_possible) { if (just_reopen) { result = reopenAceLogFile(&logfile_out) ; } else { BOOL timestamp = FALSE ; char *log_name = "log" ; ACEOUT new_logfile = NULL ; if (old_log_name) { log_name = old_log_name ; } else timestamp = TRUE ; logfile_possible = FALSE; /* Prevent logging by below calls. */ if (logfile_out) { if ((new_logfile = startNewAceLogFile(logfile_out, log_name, timestamp, log_msg, new_session))) result = TRUE ; } else { result = openAceLogFile("log", &new_logfile, new_session) ; } if (result) logfile_out = new_logfile ; } if (result) { logfile_possible = TRUE ; } } return result ; } /* registered to be the messDumpRoutine for messdump * PARAMETERS: * text : the message to be logged * may be NULL, if we just want to init the logfile * but not actually log a message * user_pointer : unused */ static void acedbLog (char *text, void *user_pointer_unused) { if (!logfile_possible) return ; /* open the file - establish status */ if (!logfile_out) { if (!(openAceLogFile("log", &logfile_out, TRUE))) logfile_possible = FALSE; else logfile_possible = TRUE; } /* A signal handler may have asked for a new log to be started... */ if (logfile_request_new_G && logfile_out) { logfile_request_new_G = FALSE ; /* Must come first to prevent possible recursion between any messXXX and here. */ if (!startNewLogFile(FALSE, NULL, NULL, FALSE)) { messdump("Could not honour requested switch to new log file.") ; } } /* Log the message if ... */ if (text != NULL && /* if we're not initialising */ logfile_out != NULL) /* if we have a log-file */ { appendAceLogFile(logfile_out, text) ; } return; } /* acedbLog */ /* Close the log file and put in a closing message for the session. */ /* */ static void acedbCloseLog(aceLogExitType exit_type) { if (logfile_out) { closeAceLogFile(logfile_out, exit_type) ; logfile_out = NULL ; logfile_possible = FALSE; } return ; } /************************************************************/ /* */ /* session callback routine registration, needed for allowing other bits of */ /* acedb to take special actions on initialisation etc., e.g. both graph and */ /* the server code need to do special things. */ /* */ /* Register the graph callbacks, this routine is expected to be called */ /* before sessionInit(). This is _not_ some generalised interface, it is */ /* simply a way to allow the server to take some graph-specific actions */ /* at various points. Note that all functions must be provided. */ /* NOTE that we expect the caller to keep their struct around and allocated, */ /* we do not take a copy so that the caller can change dynamically the */ /* routines/data that we point at. */ /* */ /* This routine is not thread safe, but we shouldn't be threading here */ /* anyway. */ void sessionRegisterGraphCB(GraphSessionCB callbacks) { if (callbacks == NULL || callbacks->exit_msg_cb == NULL || callbacks->crash_msg_cb == NULL) messcrash("Registering of graph callbacks failed, " "a NULL callback struct or NULL callback routine was passed.") ; else graphCBs_G = callbacks ; return ; } /************************************************************/ /* Register the server callbacks, this routine is expected to be called */ /* before sessionInit(). This is _not_ some generalised interface, it is */ /* simply a way to allow the server to take some server-specific actions */ /* on intialisation and termination of acedb. Note that all functions must */ /* be provided. */ /* NOTE that we expect the caller to keep their struct around and allocated, */ /* we do not take a copy so that the caller can change dynamically the */ /* routines/data that we point at. */ /* */ /* This routine is not thread safe, but we shouldn't be threading here */ /* anyway. */ void sessionRegisterServerCB(ServerSessionCB callbacks) { if (callbacks == NULL || callbacks->init.funcs.func == NULL || callbacks->checkFiles.funcs.func == NULL || callbacks->mess_out.user_func == NULL || callbacks->mess_err.user_func == NULL || callbacks->exit.funcs.mesgfunc == NULL || callbacks->crash.funcs.mesgfunc == NULL || callbacks->sighup.funcs.mesgfunc == NULL || callbacks->sigterm.funcs.mesgfunc == NULL) messcrash("Registering of server callbacks failed, " "a NULL callback struct or NULL callback routine was passed.") ; else serverCBs_G = callbacks ; return ; } /* Get a pointer to the serverCBs struct, needed by some routines outside */ /* of session.c (e.g. sigsubs.c) to find out whether to call server */ /* routines. */ /* */ ServerSessionCB sessionGetServerCB(void) { return serverCBs_G ; } /************************************************************/ /* Allocate and get hold of a global session control block, intent is to move * all session stuff over to a context one day. */ static SessionContext createContext(void) { SessionContext context ; context = messalloc(sizeof(SessionContextStruct)) ; context->unused = NULL ; session_context_G = context ; return context ; } SessionContext getContext(void) { return session_context_G ; } /************************************************************/ void sessionInit (char* ace_path) /* general public function */ { /* NOTE: only ever called from aceInit() */ KEY lastSession = 0; SessionContext context = NULL ; #if !(defined(MACINTOSH)) euid = geteuid(); ruid = getuid(); seteuid(ruid); if (euid != ruid && !getenv("ACEDB_NO_BANNER")) { printf("// Acedb is running for user %s.\n", getLogin (TRUE)); printf("// Acedb is inheriting database write access from user %s.\n", getLogin (FALSE)); } if (getgid() != getegid()) messerror("WARNING. Your acedb executable has a permission " "flag that inherits the group-id on execution (setgid). " "This is pointless, and will stop acedb from spawning " "external programs on systems with dynamic linking. " "Use executables with a set-user-id flag instead."); #endif /* Macro KEYKEY relies on KEY beeing 4 bytes long */ /* Furthermore, it may be assumed implicitly elsewhere */ if (sizeof(KEY) != 4) messcrash ("KEY size is %d - it must be 4 - rethink and recompile!", sizeof(KEY)) ; if (__Global_Bat != KEYMAKE(1,1)) /* something is really wrong */ messcrash ("Hand edit systags.h because KEYMAKE(1,1) = %d != __Global_Bat", KEYMAKE(1,1)) ; /* init these freeXXX first, they belong to the utilities-library, do all the acedb specific inits after that */ freeinit () ; freeOutInit () ; utilsInit () ; /* makes later calls to getLogin effectivelly threadSafe */ if (!dbPathInit (ace_path)) messExit ("Home database directory not found - you must give a " "parent directory with subdirectories database/ and wspec/ " "as a command line argument.") ; /* Check that the database directory has /database and /wspec sub directories and that the models.wrm file is in /spec. */ { char *tmp; if ((tmp = dbPathStrictFilName("wspec", 0, 0, "rd", 0)) != NULL) messfree(tmp); else messExit ("wspec directory not found - you must give a " "parent directory with subdirectories database/ and wspec/ " "as a command line argument.") ; if ((tmp = dbPathStrictFilName("database", 0, 0, "rd", 0)) != NULL) messfree(tmp); else messExit ("database directory not found - you must give a " "parent directory with subdirectories database/ and wspec/ " "as a command line argument.") ; if ((tmp = dbPathStrictFilName("wspec", "models", "wrm", "r", 0)) != NULL) messfree(tmp); else messExit("The file \"wspec/models.wrm\" could not be found " "in the parent database directory, the database must have " "this file to be usable.") ; if ((tmp = dbPathStrictFilName("wspec", "database", "wrm", "r", 0)) != NULL) messfree(tmp); else messExit("The file \"wspec/database.wrm\" could not be found " "in the parent database directory, the database must have " "this file to be usable.") ; } /* Locate a whelp directory if we can. Note that we use the contents of ${ACEDB}/whelp if we can, otherwise ${ACEDB_COMMON}/whelp, but we never mix the contents of the two. */ { char *helpDir = dbPathFilName("whelp", "", "", "rd", 0); if (helpDir) { helpSetDir(helpDir); messfree(helpDir); } } /* Set up the session context, in the end this should be returned by sessionInit() * to the application. */ context = createContext() ; /* Initialise the server call backs, must come after sessionSetPath but */ /* before crash routine registration. */ if (serverCBs_G) serverCBs_G->init.funcs.func(serverCBs_G->init.data) ; /* this is panic-memory, we claim some memory here which we'll never use. In case the program messcrash'es we free this chunk of storage, so hopefully we can exit gracefully and save our work before we have to exit */ messcrashbuf = malloc (12*1024) ; /* now register all the acedb specific crash/recovery routines */ /* NOTE, if you add an outContext or ErrorContext you should revue the */ /* code for these functions which is registered by the server below. */ { static struct messContextStruct exitContext = {acedbExit, NULL}; static struct messContextStruct crashContext = {acedbCrash, NULL}; static struct messContextStruct dumpContext = {acedbLog, NULL}; messExitRegister (exitContext); messCrashRegister (crashContext); logfile_possible = TRUE; /* make sure we try to open log */ acedbLog (NULL, NULL); /* open logfile - establish status * before we register it as the * dumpRoutine to use for messubs */ messDumpRegister (dumpContext); /* Server needs normal or error messages to go to special places. */ if (serverCBs_G) { messOutRegister(serverCBs_G->mess_out) ; messErrorRegister(serverCBs_G->mess_err) ; } } /* Call server routine so server can check for config. or lock files. */ /* NOTE, this call must come before the database is truly opened and */ /* intialised because it may exit if a database lock file is found. It will*/ /* exit directly, not via messcrash, because of interactions with inetd. */ /* Needs to be after crash routine registration so we can log errors. */ if (serverCBs_G) serverCBs_G->checkFiles.funcs.func(serverCBs_G->checkFiles.data) ; #ifdef CHRONO chronoStart () ; #endif /* The order of the initialisations is crucial */ chrono ("preInit") ; blockInit() ; /* Initialisation du cache */ lexInit() ; /* Initialisation du lexique */ pickPreInit() ; /* Enough SysClass definitions to read the session voc */ getOtherDatabaseConfigs(&sessionKeepAlive, &readlockTimeout, &suppressLock, &syncWrite, &enforceTimestamps, &db_title_G, &group_admin_G) ; if (syncWrite) /* Synchronous writing to disk, slow */ diskSetSyncWrite() ; /* but reliable. */ sessionDoInit() ; /* Checks ACEDB.wrm (and boostraps if neces.) Sets the number thisSession.session sets readlock for thisSession Reads in the lexiques etc, */ lastSession = lexLastKey(_VSession); if (lastSession == 0) messcrash("The session class is empty, Reconstruct, sorry") ; sessionStart(lastSession) ; chronoReturn () ; chrono ("realInit") ; pickInit() ; /* Class definitions */ prefInit () ; prefReadDBPrefs() ; /* Preferences */ pickCheckConsistency() ; /* Check tag values and class hierarchy */ lexAlphaClear () ; /* Prevents pointless savings */ sessionInitialize () ; /* must come after pickInit */ parseArrayInit() ; /* in quovadis.c */ bIndexInit(BINDEX_AUTO) ; /* will do only from disk */ { /* purge the readlocks directory of timed-out readlocks and report the readlocks still active (unless ACEDB_NO_BANNER is set) */ Array tmp = readlockGetSessionNumbers (getenv("ACEDB_NO_BANNER") ? FALSE : TRUE); arrayDestroy (tmp); } chronoReturn () ; return; } /* sessionInit */ /*************************************************************/ /* definitelly release all memories before exit */ void sessionShutDown (void) { #ifdef CHRONO chronoStop () ; chronoReport () ; #endif #if 0 /* called funct not yet folded in from ncbi (mieg, june 03 */ int n0, n1 ; BOOL debug = FALSE ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8s%8s\n","// Layer","Freed","Used") ; if (debug) freeOutf ("%18s%8d%8d kb\n","// Shutdown start", 0, n1 >> 10) ; n0 = n1 ; lexAlphaShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// LexAlpha", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; lexShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// Lex", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; bIndexShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// Bindex", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; bsMakePathShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// MakePaths", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; cacheShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// Cache2", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; blockShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// Blocks", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; BSshutdown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// BS", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; BTshutdown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// BT", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; OBJShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// OBJ", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; diskShutDown () ; messAllocStatus (&n1) ; if (debug) freeOutf ("%18s%8d%8d kb\n","// OBJ", (n0 - n1) >> 10, n1 >> 10) ; n0 = n1 ; /* prefShutDown () ; */ messfree (messcrashbuf) ; if (debug) freeOutf ("\n") ; freeOutShutDown () ; /* cannot use freeout after freeshutdown, which anyway uses very little */ freeshutdown () ; messAllocShutdown () ; n0 = n1 ; #endif } /*************************************************************/ /* External interface to I_own_the_passwd() function. */ BOOL sessionAdminUser(char *err_msg) { BOOL result = FALSE ; result = I_own_the_passwd(err_msg) ; return result ; } /* Check ownership of wspec/passwd.wrm file which is used to decide which */ /* users are allowed to reinitialise the database. */ /* */ /* for_what is a string like */ /* "initialize the database" or "change something important" */ /* or "enable write access" */ /* */ /* 1) following must be true: ruid == euid and rgid == egid */ /* 2) wspec/passwd.wrm must be readable */ /* 3) by default we check that ruid == uid of wspec/passwd.wrm */ /* 4) but if GROUP_ADMIN is specified in wspec/database.wrm then in addition */ /* we check whether rgid or users supplementary groups match the gid of */ /* wspec/passwd.wrm. */ /* */ /* */ BOOL I_own_the_passwd(char *for_what) { #if defined(MACINTOSH) return TRUE; #elif defined (__CYGWIN__) /* For windows 95, 98, always return TRUE, since it has no security */ if ( GetVersion() & 0x80000000) return TRUE ; #else /* normal Unixes */ struct stat status ; char *passwd_filename = 0; STORE_HANDLE handle; BOOL result = FALSE ; handle = handleCreate(); passwd_filename = dbPathStrictFilName("wspec", "passwd", "wrm", "r", handle); if (passwd_filename && stat(passwd_filename, &status) == 0) { if (ruid == status.st_uid) { result = TRUE; } else if (group_admin_G) { /* Group permissions check allowed. */ if (checkFileGroupPerm(&status)) result = TRUE ; else { messout ("Sorry, to %s, you must be in the unix group for the " "password file. Check %s !", for_what, passwd_filename); result = FALSE; } } else { messout ("Sorry, to %s, you must be the owner of the " "password file. Check %s !", for_what, passwd_filename); result = FALSE; } } else { messout ("Sorry, the database has no password file. " "To %s, the usernames of trusted administrators " "have to be listed in the file : %s/passwd.wrm", for_what, dbPathStrictFilName("wspec", "", "", "rd", handle)); result = FALSE; } handleDestroy(handle) ; return result ; #endif } /* This could be made into a general utility with a little work. */ /* */ /* Strict check of whether a users rgid or supplementary gids match a files */ /* gid. */ /* */ /* Note that some Unixes do not support supplementary groups, we test */ /* NGROUPS_MAX to look for this. */ /* */ static BOOL checkFileGroupPerm(struct stat *file_stat) { BOOL result = FALSE ; gid_t rgroup, egroup ; rgroup = getgid() ; egroup = getegid() ; if (rgroup == egroup) { if (file_stat->st_gid == rgroup) result = TRUE ; #ifdef NGROUPS_MAX else { int ngroups ; /* This looks a little arcane, basically you have to find out how */ /* many supplementary groups there are before you can find what */ /* they are...sigh... */ ngroups = getgroups(0, NULL) ; if (ngroups == -1) messcrash("Internal coding error in checkFileGroupPerm() " "the getgroups() call to find the number of supplementary " "groups failed.") ; else if (ngroups == 0) result = FALSE ; else { gid_t *groups ; groups = messalloc(sizeof(gid_t) * ngroups) ; if (getgroups(ngroups, groups) <= 0) messcrash("Internal coding error in checkFileGroupPerm() " "the getgroups() call to retrieve the supplementary " "groups failed.") ; else { int i ; gid_t gid ; gid_t *grpptr = groups ; for (i = 0 ; i < ngroups ; i++) { gid = *grpptr++ ; if (gid == file_stat->st_gid) { result = TRUE ; break ; } } } messfree(groups) ; } } #endif /* NGROUPS_MAX */ } return result ; } /*************************************************************/ /************** Disk Lock System *****************************/ /*************************************************************/ static BOOL showWriteLock (void) { static char* logName = "Don't-know-who" ; int n = 0; ACEIN lock_io = 0 ; char *filename; filename = dbPathMakeFilName("database", "lock", "wrm", 0); lock_io = aceInCreateFromFile (filename, "r", "", 0); messfree(filename); if (lock_io) { if (aceInCard(lock_io)) { aceInNext(lock_io) ; if (aceInInt(lock_io,&n)) { char *name ; aceInNext(lock_io) ; name = aceInWord(lock_io) ; if (name != NULL) logName = name ; } } aceInDestroy (lock_io) ; messout ("%s (session %d) already has write access", logName, n) ; return TRUE ; } return FALSE; } /* showWriteLock */ /**************************/ static int lockFd = -1 ; /* lock file descriptor */ static void releaseWriteLock (void) { char *filename = NULL; seteuid(euid); if (lockFd == -1) messcrash ("Unbalanced call to releaseWriteLock") ; if (!suppressLock) if (lockf (lockFd, F_ULOCK, 0)) /* non-blocking */ messcrash ("releaseWriteLock cannot unlock the lock file"); close (lockFd) ; filename = dbPathStrictFilName("database", "lock", "wrm", "w", 0); if ((!filename) || (!filremove(filename, ""))) messerror ("Cannot unlock database " "by removing database/lock.wrm (%s)", messSysErrorText()) ; if (filename) messfree(filename); lockFd = -1 ; seteuid(ruid); messdump ("Write access dropped") ; } /* releaseWriteLock */ /**************************/ static BOOL setWriteLock (void) { unsigned int size ; char *cp ; /* should never fail */ char *filename = dbPathMakeFilName("database", "lock", "wrm", 0); if (lockFd != -1) messcrash ("Unbalanced call to setWriteLock") ; if (suppressLock) { /* check for existence of unlocked lock.wrm file if it exists, then the database is considered locked */ lockFd = open(filename, O_RDONLY); if(lockFd != -1) /* open was successful * the lock-file exists */ { showWriteLock(); /* show the owner of the lock */ close(lockFd); lockFd = -1; messfree(filename); return FALSE; /* can't get lock */ } /* OK so we don't have a lock-file .... */ } /* open the file regardless whether the file is there, * create it if it's not there and open in readwrite mode */ seteuid(euid); lockFd = open (filename, O_RDWR | O_CREAT | O_SYNC, 0644) ; if (lockFd == -1) { /* is there a permissions problem ? */ /* shouldn't have happened, if * sessionDatabaseWritable() succeeded... */ messerror ("I can't open or create the lock file %s. (%s)", filename, messSysErrorText()) ; seteuid(ruid); messfree(filename); return FALSE ; } if (!suppressLock) { /* on systems that permit file-locking we make use of lockf(3) * on the actual lockfile. Its advantage is that the kernel-lock * will be cleaned up even when the program dies and * leaves the file hanging around. */ /* THERE IS A BIG PROBLEM HERE WITH NFS, IF THE FILE EXISTS ON A */ /* REMOTE FILE SYSTEM AND THE LOCK DAEMON IS NOT WORKING PROPERLY, */ /* THE CODE WILL HANG ON THIS TEST AND NOT RETURN. There is nothing we */ /* can do about this because our NFS mounts at sanger are "hard" mounts*/ if (lockf (lockFd, F_TLOCK, 0)) /* non-blocking test for lock */ { if (errno == EACCES) { /* "Permission denied" - standard case * can't lock file - show who has locked locked it */ showWriteLock(); } else { /* other error */ /* could be the errno ENOLCK - "No locks available" hints to a possible misconfiguration of the lockdaemon, see man-page lockf(3) */ messout ("Sorry, I am unable to establish a lock " "on file database/lock.wrm\n" "(%s)\n" "There may be a problem with the lock daemon " "on this machine, esp. if running NFS.\n" "To avoid the use of the lock daemon, " "insert the word NOLOCK\n" "in the file wspec/database.wrm", messSysErrorText()); } close(lockFd); lockFd = -1 ; seteuid(ruid); messfree(filename); return FALSE ; } } /* write lock-owner info into the lock-file */ cp = messprintf ("%d %s\n\n", thisSession.session, getLogin(TRUE)) ; size = strlen(cp) + 1 ; write (lockFd, cp, size) ; seteuid(ruid); messdump ("Write access"); messfree(filename); return TRUE; } /* setWriteLock */ /****************************************************************/ /****************************************************************/ /* If thisSessionStartTime == 0 then no session has currently been started we set the start time here but only create the userSession key in sessionUserKey in case it is never needed. */ static mytime_t thisSessionStartTime = 0 ; /* set start time of session and init userKey */ void sessionInitialize (void) { thisSessionStartTime = timeNow() ; thisSession.userKey = 0 ; lexSessionStart () ; return ; } /* sessionInitialize */ /************************************************************/ void sessionStart (KEY fromSession) /* private function in session module */ { OBJ Session ; int a3 = 0, b3 = 0, c3 = 0, h3 = 0, parent_index_version = 0 ; Session = bsCreate(fromSession) ; if(!Session ) { messout("Sorry, I cannot find %s", name(fromSession)) ; return ; } if(!bsGetData(Session,_VocLex,_Int,&a3) || !bsGetData(Session,_bsRight,_Int,&b3) || !bsGetData(Session,_bsRight,_Int,&c3) || /* Hasher only introduced in release 1.4 !bsGetData(Session,_bsRight,_Int,&h0) || */ !bsGetData(Session,_CodeRelease, _Int,&thisSession.mainCodeRelease) || !bsGetData(Session,_bsRight, _Int,&thisSession.subCodeRelease) || !bsGetData(Session,_DataRelease, _Int,&thisSession.mainDataRelease) || !bsGetData(Session,_bsRight, _Int,&thisSession.subDataRelease) ) { messout("No address in this session") ; bsDestroy(Session) ; return ; } bsGetData(Session,_IndexVersion, _Int,&parent_index_version) ; bsDestroy(Session) ; thisSession.from = fromSession ; thisSession.upLink = fromSession ; thisSession.index_version = bIndexVersion(parent_index_version) ; lexOverLoad(__lexi3,(DISK)a3) ; lexOverLoad(__voc3,(DISK)c3) ; lexOverLoad(__lexh3,(DISK)h3) ; lexRead() ; return; } /* sessionStart */ /*************************************************************/ static BOOL sessionOedipe (void) /* Kill the father of the present session */ { KEY father = thisSession.upLink, grandFather, fPlus, fMinus ; OBJ Father ; Array fPlusArray, fMinusArray ; if(!father || !(Father = bsUpdate(father))) return FALSE ; if(!bsGetKey(Father,_BatPlus,&fPlus) || !bsGetKey(Father,_BatMinus,&fMinus) ) { messout("No address in this father") ; bsDestroy(Father) ; return FALSE ; } if(!bsFindTag(Father,_Permanent_session)) { messout("The previous session being delared permanent, I keep it") ; bsDestroy(Father) ; return FALSE ; } if (bsGetKey(Father, _Up_linked_to, &grandFather)) { thisSession.upLink = grandFather ; bsAddData(Father, _Destroyed_by_session, _Int, &thisSession.session) ; bsSave(Father) ; } else { thisSession.upLink = 0 ; bsDestroy(Father) ; } fPlusArray = arrayGet(fPlus,unsigned char,"c") ; fMinusArray = arrayGet(fMinus,unsigned char,"c") ; diskFuseOedipeBats(fPlusArray, fMinusArray) ; arrayDestroy(fPlusArray) ; arrayDestroy(fMinusArray) ; /* Kill the father bats */ arrayKill(fPlus) ; arrayKill(fMinus) ; saveAll() ; return TRUE ; } /* sessionOedipe */ /******************/ static BOOL sessionFuse (KEY father, KEY son) { KEY fPlus, fMinus, sPlus, sMinus, grandFather ; OBJ Father = 0 , Son = 0 ; int nPlus = 0, nMinus = 0 ; Father = bsUpdate(father) ; Son = bsUpdate(son) ; if(!Father || !Son || !bsGetKey(Father,_BatPlus,&fPlus) || !bsGetKey(Father,_BatMinus,&fMinus) || bsFindTag(Father, _Destroyed_by_session) || !bsGetKey(Son,_BatPlus,&sPlus) || !bsGetKey(Son,_BatMinus,&sMinus) ) goto abort ; if (!diskFuseBats(fPlus, fMinus, sPlus, sMinus, &nPlus, &nMinus)) goto abort ; /* Store the sons bat sizes */ bsAddKey(Son, _BatPlus, sPlus) ; /* to reposition */ bsAddData(Son, _bsRight, _Int, &nPlus) ; bsAddKey(Son, _BatMinus, sMinus) ; bsAddData(Son, _bsRight, _Int, &nMinus) ; bsAddData(Father, _Destroyed_by_session, _Int, &thisSession.session) ; if (bsGetKey(Father, _Up_linked_to,&grandFather)) bsAddKey(Son , _Up_linked_to, grandFather) ; bsSave(Father) ; bsSave(Son) ; saveAll() ; return TRUE ; abort: bsSave(Son) ; bsSave(Father) ; return FALSE ; } /* sessionFuse */ /************************************************************/ Array sessionTreeCreate(BOOL showDeads) /* private function in session module */ { KEY key = 0 , kk ; int treeArrayMax ; char *cp ; OBJ obj ; ST *st , *st1 ; int gen, i, j; BOOL found ; Array sessionTree; messStatus ("Reading sessions"); sessionTree = arrayCreate(50, ST) ; /* Add all Session objects to the session tree */ treeArrayMax = 0; while (lexNext(_VSession, &key)) if ((obj = bsCreate(key))) { st = arrayp(sessionTree, treeArrayMax++, ST) ; st->key = key ; bsGetData(obj,_Session, _Int, &st->number); if (bsGetKey(obj,_Created_from, &kk)) st->father = st->ancester = kk ; if (bsGetKey(obj,_Up_linked_to, &kk)) st->ancester = kk ; else if (st->key != KEYMAKE(_VSession, 2)) st->isDestroyed = TRUE ; /* i.e. to old a release */ if (bsFindTag(obj,_Destroyed_by_session)) st->isDestroyed = TRUE ; if (bsFindTag(obj,_Permanent_session)) st->isPermanent = TRUE ; if (bsGetData(obj,_User, _Text, &cp)) st->user = strnew(cp, 0); if (bsGetData(obj,_Date, _Text, &cp)) st->date = strnew(cp, 0); if (bsGetData(obj,_Session_Title, _Text, &cp) && strlen(cp) > 0) st->title = strnew(cp, 0); else st->title = strnew(name(st->key), 0); st->len = strlen(st->title); bsDestroy(obj) ; } /* Add thisSession to the session tree (the object for the current session is not a finalized object yet and therfore won't show up in the lexer up) */ st = arrayp(sessionTree, treeArrayMax++, ST) ; st->key = _This_session ; st->father = st->ancester = thisSession.from ; st->title = strnew(name(st->key), 0); st->len = strlen(st->title); /* links the st */ for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) for (j = 0, st1 = arrp(sessionTree,0,ST) ; j < arrayMax(sessionTree) ; st1++, j++) if(st->key == st1->ancester) st1->sta = st ; /* Count generations */ if (showDeads) { for (i = 0, st = arrp(sessionTree,0,ST); i < arrayMax(sessionTree) ; st++, i++) if (!st->ancester) st->generation = 1 ; } else { for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) if ((!st->isDestroyed || st->isReadlocked) && ( !st->ancester || (st->sta && st->sta->isDestroyed)) ) st->generation = 1 ; } gen = 1 ; found = TRUE ; while (found) { found = FALSE ; if (showDeads) { for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) if (st->sta && st->sta->generation == gen) { st->generation = gen + 1 ; found = TRUE ; } } else { for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) if ((!st->isDestroyed || st->isReadlocked) && st->sta && st->sta->generation == gen) { st->generation = gen + 1 ; found = TRUE ; } } gen++ ; } /* mark readlocked sessions, they won't get destroyed, because other processes still have that data cached */ { Array readlockedSessions; int n, locked_num; if ((readlockedSessions = readlockGetSessionNumbers(FALSE))) { for (n = 0; n < arrayMax(readlockedSessions); n++) { locked_num = arr (readlockedSessions, n, int); for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) /* locked_num - 1 is the session that the process (that keeps the readlock) was initialised from - that is the number that needs to be protected. */ if (st->number == locked_num - 1) st->isReadlocked = TRUE; } arrayDestroy (readlockedSessions); } } return sessionTree; } /* sessionTreeConstruct */ /************************************************************/ void sessionTreeDestroy(Array sessionTree) /* private function in session module */ { ST *st ; int i; if (!arrayExists(sessionTree)) messcrash("sessionTreeDestroy() - called with invalid sessionTree"); for (i = 0, st = arrp(sessionTree, 0, ST); i < arrayMax(sessionTree); st++, i++) { if (st->user) messfree (st->user); if (st->date) messfree (st->date); if (st->title) messfree (st->title); } arrayDestroy (sessionTree); return; } /* sessionTreeDestroy */ /************************************************************/ static int sessionFindSon(Array sessionTree, KEY father, KEY *sonp) { int i, n = 0; ST *st, *sta ; *sonp = 0 ; /* Suicide */ if (father == _This_session) return -2 ; if (!father) /* Trivial */ return -3 ; if (father <= KEYMAKE(_VSession, 2)) return -6 ; for (i = 0, sta = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; sta++, i++) if (sta->key == father) break ; if (sta->key != father) /* not found error */ return -5 ; if (sta->isDestroyed) /* dead */ return -3 ; for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) if(!st->isDestroyed && st->sta == sta) { *sonp = st->key ; n++ ; } if (n == 1) /* is st1 in my direct ancestors */ { for (i = 0, st = arrp(sessionTree,0,ST) ; i < arrayMax(sessionTree) ; st++, i++) if (st->key == thisSession.from) { while (st) { if (st == sta) return 1 ; st = st->sta ; } return -4 ; } return -4 ; /* not ancestor */ } return n ; /* number of sons */ } /* sessionFindSon */ /************************************************************/ static BOOL sessionKillAncestors (Array sessionTree, int n) /* Kill all father over level n */ { KEY father = thisSession.upLink, son ; ST *st ; int i ; for (i = 0, st = arrp(sessionTree,0,ST); i < arrayMax(sessionTree); st++, i++) if (st->key == father) break ; if (father && st->key != father) { messerror ("sessionKillAncestors() - " "could not find the father he wanted to murder.") ; return FALSE ; } while (st && --n) /* Move up the ladder n times */ st = st->sta ; if (!st || n) /* Not many ancesters alive */ return FALSE ; while (st && (st->isPermanent || st->isReadlocked || sessionFindSon(sessionTree, st->key, &son) != 1)) st = st->sta ; /* Try higher up */ if (st && st->key && son) return sessionFuse(st->key, son) ; return FALSE ; } /* sessionKillAncestors */ /*************************************************************/ static char* getConfiguredDatabaseName (void) { static char dbname[32] = ""; char *word ; ACEIN db_io = 0 ; char *filename; filename = dbPathMakeFilName("wspec", "database", "wrm", 0); db_io = aceInCreateFromFile (filename, "r", "", 0); messfree(filename); if (db_io); { while (aceInCard(db_io)) if ((word = aceInWord(db_io)) && strcmp (word, "NAME") == 0 && (word = aceInWord(db_io))) { if (strlen (word) > 31) messExit ("Error in wspec/database.wrm : " "the NAME is too long (31 characters max).") ; else strcpy (dbname, word) ; } aceInDestroy (db_io) ; } return &dbname[0]; } /* getConfiguredDatabaseName */ /*************************************************************/ /* Set any default value overrides from wspec/database.wrm */ static void getOtherDatabaseConfigs(int *keepAlive, int *timeOut, BOOL *suppressLock, BOOL *syncWrite, BOOL *enforceTs, char **db_title, BOOL *group_admin) { char *word ; ACEIN db_io = 0 ; char *filename; filename = dbPathMakeFilName("wspec", "database", "wrm", 0); db_io = aceInCreateFromFile (filename, "r", "", 0); messfree(filename); if (db_io) { while (aceInCard(db_io)) { if ((word = aceInWord(db_io))) { if (strcmp (word, "SESSION_KEEP_ALIVE") == 0) { BOOL rc ; rc = aceInInt(db_io,keepAlive) ; if (rc == FALSE || *keepAlive < SESSKEEPALIVE_MIN) messExit ("Error in wspec/database.wrm : " "smallest legal value for SESSION_KEEP_ALIVE is %d", SESSKEEPALIVE_MIN) ; } else if (strcmp (word, "READLOCK_TIMEOUT") == 0) { BOOL rc ; rc = aceInInt(db_io,timeOut) ; if (rc == FALSE || *timeOut < RDLCKTIMEOUT_MIN) messExit ("Error in wspec/database.wrm : " "smallest legal value for READLOCK_TIMEOUT is %d hours", RDLCKTIMEOUT_MIN) ; } else if (strcmp(word, "NOLOCK") == 0) { *suppressLock = TRUE ; } else if (strcmp(word, "SYNC_WRITE") == 0) { *syncWrite = TRUE ; } else if (strcmp(word, "ENFORCE_TIMESTAMPS") == 0) { *enforceTs = TRUE; } else if (strcmp(word, "TITLE") == 0) { word = aceInPos(db_io) ; if (word && *word) *db_title = strnew(word, 0) ; } else if (strcmp(word, "GROUP_ADMIN") == 0) { *group_admin = TRUE ; } } } aceInDestroy (db_io) ; } return; } /* getOtherDatabaseConfigs */ static BOOL purgeDirectory (char *directoryPath) { BOOL isError = FALSE; int n; char fullFileName[MAXPATHLEN]; FilDir dirList; if (!directoryPath) return FALSE; dirList = filDirCreate (directoryPath, 0, "w"); /* list all files that we can potentially delete */ for (n = 0; n < filDirMax(dirList); n++) { strcpy (fullFileName, directoryPath); strcat (fullFileName, SUBDIR_DELIMITER_STR); strcat (fullFileName, filDirEntry(dirList, n)); if (unlink(fullFileName) == -1) isError = TRUE; } messfree (dirList); return isError; } /* purgeDirectory */ /*************************************************************/ static void sessionDoInit(void) { FILE *f ; int mainRelease; char *filename; char *log_msg = NULL ; if ((filename = dbPathStrictFilName ("database", "ACEDB","wrm","r", 0)) != NULL) { messfree(filename); /* this doesn't swap the superblock (write does) */ #ifndef ACEDB5 dataBaseAccessInit(); diskblockread (&theSuperBlock,1) ; if (theSuperBlock.byteSexIndicator != 1) { swapData = TRUE; swapSuperBlock(&theSuperBlock); if (!getenv("ACEDB_NO_BANNER")) printf("// Database has non-native byte-order: swapping\n"); } else { swapData = FALSE; } /* In order to stop old database code running on databases which have timestamps, we zero the mainrelease value in the superblock. Hence old code will fail the test below. For code in the know, the real mainrelease value is held elsewhere in the superblock. Note that we only implement this if ENFORCE_TIMETSTAMPS is set in database.wrm */ if (theSuperBlock.mainRelease) { mainRelease = theSuperBlock.mainRelease; if (enforceTimestamps) { theSuperBlock.alternateMainRelease = mainRelease; theSuperBlock.mainRelease = 0; } } else mainRelease = theSuperBlock.alternateMainRelease; #else /* ACEDB5 */ aDiskAccessInit(&theSuperBlock); mainRelease = theSuperBlock.mainRelease; #endif /* ACEDB5 */ if (mainRelease != aceGetVersion()) { if (getenv ("ACEDB_CHANGE_MAIN_RELEASE")) /* override */ { #ifdef ACEDB4 if (theSuperBlock.mainRelease) theSuperBlock.mainRelease = aceGetVersion() ; else theSuperBlock.alternateMainRelease = aceGetVersion(); #else theSuperBlock.mainRelease = aceGetVersion() ; #endif } else messExit ("You are running %s of the code.\n" "The database was created under release %d.\n" "Either use the correct binary or upgrade the database.\n", aceGetVersionString(), theSuperBlock.mainRelease) ; } #ifdef ACEDB5 thisSession.session = ++theSuperBlock.session ; #else thisSession.session = ++theSuperBlock.h.session ; #endif thisSession.gAddress = theSuperBlock.gAddress ; lexOverLoad (__lexi1,theSuperBlock.gAddress) ; lexReadGlobalTables () ; if (!readlockCreateFile()) fprintf (stderr, "Unable to set readlock for session %d.\n" "The database owner should run an acedb program\n" "with this new version (%s) to prepare the\n" "database for readlocks.\n", thisSession.session, aceGetVersionString()); return ; } /* else reinitialise the system */ /* Boolean forceInit_G indicates that if the main program was called with -init ** then the database is to be initialised without further prompting. */ if (forceInit_G == FALSE) { if (!messQuery ("The file database/ACEDB.wrm does not exist, \n" "indicating that the database is empty.\n" " Should I re-initialise the system?")) messExit ("Database will remain uninitialized. Bye!") ; } /* make sure we start the new database with a fresh log-file, do that */ /* before anything else writes to the current log-file. */ log_msg = hprintf(0, "Database is being rebuilt by %s - End of this logfile", getLogin(TRUE)) ; if (!startNewLogFile(FALSE, NULL, log_msg, TRUE)) messExit ("Cannot start new log file for new session ! Initialization failed !") ; messfree(log_msg) ; /* Only the acedb administrator is entitled to proceed * I check this by (a) getuid == geteuid * (b) checking for a password file * (c) testing that the user owns the file or * if GROUP_ADMIN = TRUE that the user is * is in the group for the password file. * (d) testing that the user is listed in that file * In the install documentation, it is stated that * passwd.wrm should be set chmod 644, but 444 is OK, * once the file is set-up */ if (!I_own_the_passwd ("initialise the database") || !sessionCheckUserName (TRUE)) messExit ("Not authorized to initialize the database.\n" "Check wspec/passwd.wrm"); if (!setWriteLock()) /* RARE occurence, but this can happen if another process is also in the middle of the initialization process at the same time */ messExit ("Cannot set write lock! Initialization failed!") ; /****************************************************************/ /****** all permissions clear, boostrap an empty database *******/ /****************************************************************/ { char *tmp; if ((tmp = dbPathStrictFilName("database", "new", 0, "wd", 0)) != NULL) { purgeDirectory(tmp); messfree(tmp); } if ((tmp = dbPathStrictFilName("database", "touched", 0, "wd", 0)) != NULL) { purgeDirectory(tmp); messfree(tmp); } if ((tmp = dbPathStrictFilName("database", "readlocks", 0, "wd", 0)) != NULL) { purgeDirectory(tmp); messfree(tmp); } } thisSession.session = 0; /* special session number to be used for this first readlock will be updated by sessionDoClose() called at the end of this function */ if (!readlockCreateFile()) messExit ("Unable to set readlock in database/readlocks/"); /****************************************************************/ writeAccess = TRUE ; /* implied */ swapData = FALSE ; /* The database file must be in our order */ thisSession.session = 1 ; strcpy (thisSession.name, "Empty_db") ; thisSession.gAddress = 0 ; /* so far */ thisSession.mainCodeRelease = aceGetVersion() ; thisSession.subCodeRelease = aceGetRelease() ; thisSession.mainDataRelease = aceGetVersion() ; thisSession.subDataRelease = 0 ; thisSession.from = 0 ; thisSession.upLink = 0 ; #ifndef ACEDB5 theSuperBlock.h.disk = 1 ; theSuperBlock.h.nextdisk = 0 ; theSuperBlock.h.type = 'S' ; theSuperBlock.h.key = 0 ; theSuperBlock.byteSexIndicator = 1; if (enforceTimestamps) { theSuperBlock.mainRelease = 0; theSuperBlock.alternateMainRelease = aceGetVersion(); } else theSuperBlock.mainRelease = aceGetVersion() ; #endif strcpy (theSuperBlock.dbName, getConfiguredDatabaseName()); /* DataBase creation */ if (!dataBaseCreate()) messExit ("Database Creation failed, check write permissions etc.") ; messdump("**********************************************************"); messdump("Making a new database file and starting at session 1 again"); readModels (); /* application specific initialisations, in quovadis.wrm */ diskPrepare (); sessionInitialize (); /* do this last before saving */ filename = dbPathMakeFilName("database", "ACEDB", "wrm", 0); if (!(f = filopen (filename, 0 , "w"))) messExit ("Sorry, the operating system won't let me create %s", filename) ; fprintf (f, "Destroy this file to reinitialize the database\n") ; filclose (f) ; messfree(filename); filename = dbPathStrictFilName("database", "lock", "wrm", "w", 0); f = filopen (filename, 0, "w") ; fprintf (f, "The lock file\n") ; filclose (f) ; messfree(filename); readModels () ; /* second read needed to register the _VModel obj */ bIndexInit(BINDEX_AUTO) ; sessionDoClose () ; /* real save without sessionInitialize will also update readlock-file */ } /* sessionDoInit */ /************************************************************/ /* Used by status.c and display.c to get users title for the database. */ /* */ char *sessionDBTitle(void) { return db_title_G ; } char *sessionDbName (void) /* general public function */ { #ifdef ACEDB4 int mainRelease = theSuperBlock.mainRelease ? theSuperBlock.mainRelease : theSuperBlock.alternateMainRelease; #else int mainRelease = theSuperBlock.mainRelease; #endif /* returns the name of the database as gathered from the superblock, used for the window title and messages in data updates */ if (mainRelease < 4 || theSuperBlock.subCodeRelease < 3) return 0 ; /* not implemented before 4.3 */ else if (*theSuperBlock.dbName) return theSuperBlock.dbName ; else return 0 ; } /* sessionDbName */ /* Return last time/date the database was saved. */ char *sessionLastSaveTime(void) { char *save_time = NULL, *cp ; KEY last_session ; OBJ obj ; last_session = thisSession.upLink ; if (!(obj = bsCreate(last_session))) messcrash("Unable to open last session object from its key: \"%s\"", name(last_session)) ; if (!(bsGetData(obj,_Date, _Text, &cp))) messcrash("Unable to get \"Date\" from last session object: \"%s\"", name(last_session)) ; save_time = strnew(cp, 0); bsDestroy(obj) ; return save_time ; } /************************************************************/ static void sessionSaveSessionObject(void) { /* store the Session Object */ /* update the BAT block access table for thisSession */ DISK d ; OBJ Session ; KEY kPlus, kMinus ; int free2, plus, minus, used, ndread,ndwrite ; Session = bsUpdate (thisSession.key) ; if(!Session) messcrash("Session register fails to create the session object") ; if (thisSession.session == 1) bsAddTag(Session,_Permanent_session) ; bsAddData(Session,_User,_Text,&thisSession.name) ; if (!bsFindTag (Session, _Date)) /* don't add date twice - it changes => save loop */ { bsAddTag (Session, _Date) ; if (bsType (Session, _bsRight) == _Text) /* dates used to be _Text, we have to account for that */ bsAddData (Session, _Date, _Text, timeShow(timeNow())) ; else /* with recent versions, the date is _DateType */ /* ERRRRRMMMMM, this seems not to be true...see definition in whooks/sysclass.c */ { mytime_t session_date = timeNow() ; bsAddData (Session, _Date, _DateType, &session_date) ; } } if (*thisSession.title) bsAddData(Session,_Session_Title,_Text,thisSession.title) ; bsAddData(Session,_Session,_Int,&thisSession.session) ; if(thisSession.from) bsAddKey(Session,_Created_from,thisSession.from) ; if(thisSession.upLink) bsAddKey(Session,_Up_linked_to,thisSession.upLink) ; thisSession.mainCodeRelease = aceGetVersion() ; thisSession.subCodeRelease = aceGetRelease() ; bsAddData(Session,_CodeRelease, _Int,&thisSession.mainCodeRelease) ; bsAddData(Session,_bsRight, _Int,&thisSession.subCodeRelease) ; bsAddData(Session,_DataRelease, _Int,&thisSession.mainDataRelease) ; bsAddData(Session,_bsRight, _Int,&thisSession.subDataRelease) ; { int v = bIndexVersion(-1) ; if (v) bsAddData(Session,_IndexVersion, _Int,&v) ; } diskavail(&free2,&used,&plus,&minus,&ndread,&ndwrite) ; d = lexDisk(__Global_Bat) ; bsAddData(Session,_GlobalBat,_Int,&d) ; bsAddData(Session,_bsRight,_Int,&used) ; d = lexDisk(__lexi1) ; bsAddData(Session,_GlobalLex,_Int,&d) ; d = lexDisk(__lexi2) ; bsAddData(Session,_SessionLex,_Int,&d) ; d = lexDisk(__lexa2) ; bsAddData(Session,_bsRight,_Int,&d) ; d = lexDisk(__voc2) ; bsAddData(Session,_bsRight,_Int,&d) ; d = lexDisk(__lexh2) ; bsAddData(Session,_bsRight,_Int,&d) ; d = lexDisk(__lexi3) ; bsAddData(Session,_VocLex,_Int,&d) ; d = lexDisk(__lexa3) ; bsAddData(Session,_bsRight,_Int,&d) ; d = lexDisk(__voc3) ; bsAddData(Session,_bsRight,_Int,&d) ; d = lexDisk(__lexh3) ; bsAddData(Session,_bsRight,_Int,&d) ; lexaddkey(messprintf("p-%d",thisSession.session),&kPlus,_VBat) ; lexaddkey(messprintf("m-%d",thisSession.session),&kMinus,_VBat) ; bsAddKey(Session,_BatPlus,kPlus) ; bsAddData(Session,_bsRight,_Int,&plus) ; bsAddKey(Session,_BatMinus,kMinus) ; bsAddData(Session,_bsRight,_Int,&minus) ; bsSave(Session) ; } /* sessionSaveSessionObject */ /************************************************************/ static void sessionSaveUserSessionObject (void) { /* complete the UserSession object */ /* this might be called several times over in the process of saveAll() */ OBJ obj ; KEY key, tag ; mytime_t timEnd = timeNow() ; lexSessionEnd () ; /* writes new and touched keysets */ sessionUserKey () ; obj = bsUpdate (thisSession.userKey) ; bsAddKey (obj, _Session, thisSession.key) ; bsAddData (obj, _User, _Text, thisSession.name) ; /* don't change dates again, once set, to avoid causing saveAll() to think that modifications have happened in the multiple saving loop */ if (!bsFindTag (obj, _Start)) { if (!thisSessionStartTime) messcrash ("Can't save UserSession object " "with unititialized StartTime"); bsAddData (obj, _Start, _DateType, &thisSessionStartTime) ; } if (!bsFindTag (obj, _Finish)) bsAddData (obj, _Finish, _DateType, &timEnd) ; if (lexword2key (messprintf ("new-%s", name(thisSession.userKey)), &key, _VKeySet)) { lexaddkey ("New", &tag, 0) ; bsAddKey (obj, tag, key) ; } if (lexword2key (messprintf ("touched-%s", name(thisSession.userKey)), &key, _VKeySet)) { lexaddkey ("Touched", &tag, 0) ; bsAddKey (obj, tag, key) ; } bsSave (obj) ; } /* sessionSaveUserSessionObject */ /**************************************************/ mytime_t sessionUserSession2Time(KEY userSession) { OBJ obj; static Associator key2time = NULL; void *v; mytime_t time; if (!key2time) key2time = assCreate(); if (!userSession) return 0; /* If the timestamp is for th current session, the data will not be in the object yet */ if (userSession == sessionUserKey()) return sessionStartTime(); if (assFind(key2time, assVoid(userSession), &v)) return (mytime_t) assInt(v); time = 0; if ((obj = bsCreate(userSession))) { bsGetData(obj, str2tag("Start"), _DateType, &time); bsDestroy(obj); } assInsert(key2time, assVoid(userSession), assVoid(time)); return time; } /************************************************************/ void sessionRegister(void) /* general public function */ { /* this is called several times over during saveAll() */ OBJ ModelObj ; if (!isWriteAccess()) return ; #ifdef ACEDB4 { OBJ obj1 = bsCreate (KEYMAKE (_VUserSession,0)) ; if (!obj1) { messout ("You are missing the model for UserSession. " "To bring your database up to date please Read Models. " "You will not be able to save until you do this.") ; return ; } bsDestroy (obj1) ; } #endif strncpy (thisSession.name, getLogin(TRUE), 78) ; lexaddkey (messprintf ("s-%d-%s", thisSession.session, thisSession.name), &thisSession.key, _VSession) ; #ifdef ACEDB4 ModelObj = bsCreate(KEYMAKE(_VSession,0)) ; #else { KEY ModelKey ; lexaddkey ("#Session", &ModelKey, _VModel) ; ModelObj = bsCreate(ModelKey) ; } #endif if(!ModelObj || !bsFindTag(ModelObj,_Up_linked_to)) /* Always check on latest tag added to ?Session */ { messerror("%s\n%s\n%s\n%s\n%s\n", "FATAL, an eror occured in sessionRegister", "Probably, your model for the session class is not correct", "and corresponds to an earlier release.", "Look for the latest version of the wspec/models.wrm file,", "read the model file and try again.") ; exit(1) ; } bsDestroy(ModelObj) ; sessionSaveSessionObject() ; sessionSaveUserSessionObject() ; /* will be reset for next new session (in sessionInitialize) */ thisSessionStartTime = 0 ; } /* sessionRegister */ /*************************************************************/ /* returns TRUE if modifications had been saved */ BOOL sessionDoClose (void) { BOOL savedSomething = FALSE ; KEY son ; if (!isWriteAccess()) return FALSE ; /* Try to save all caches - return TRUE, if there had been modifications that were saved to disk*/ if (saveAll()) { /* Yes we saved modifications - adjust the session structure*/ /* I keep only a certain number of session alive (the sessionKeepAlive static int) */ if (thisSession.session != 1) { Array sessionTree; sessionTree = sessionTreeCreate (FALSE); /* exclude dead sessions */ if (sessionKeepAlive != 1) while (sessionKillAncestors(sessionTree, sessionKeepAlive)) { sessionTreeDestroy(sessionTree); sessionTree = sessionTreeCreate (FALSE); } else if (sessionFindSon(sessionTree, thisSession.upLink, &son) == 1) sessionOedipe () ; sessionTreeDestroy (sessionTree); } /****************************/ /* increment session number */ /****************************/ /* if you edit this code, edit also readsuperblock and sessionDestroy */ theSuperBlock.gAddress = thisSession.gAddress = lexDisk(__lexi1) ; #ifdef ACEDB4 if (!theSuperBlock.mainRelease) theSuperBlock.alternateMainRelease = aceGetVersion() ; else #endif theSuperBlock.mainRelease = aceGetVersion() ; theSuperBlock.subCodeRelease = aceGetRelease() ; theSuperBlock.magic = saveMagic ; thisSession.from = thisSession.key ; thisSession.upLink = thisSession.key ; #ifdef ACEDB5 theSuperBlock.session = thisSession.session ; #else theSuperBlock.h.session = thisSession.session ; #endif diskWriteSuperBlock(&theSuperBlock); /* writes and zeros the BATS*/ messdump ("Save session %d", thisSession.session) ; thisSession.session++; /* increase current session number */ readlockUpdateFile (); /* update readlock for next session */ savedSomething = TRUE ; if (sessionChangeRoutine) (*sessionChangeRoutine)(); } /* else savedSomething is still FALSE */ sessionReleaseWriteAccess() ; if (externalCommit) externalCommit() ; return savedSomething ; } /* sessionDoClose */ /*************************************************************/ BOOL isWriteAccess(void) /* general public function */ { return writeAccess ? TRUE : FALSE ; } /* isWriteAccess */ /*************************************************************/ /* gains write access for the user, if possible */ BOOL sessionGainWriteAccess(void) { BOOL write_lock = FALSE ; if (writeAccess) /* already has write access */ return TRUE; /* We don't have write access, but can we grab it ?? */ /* The tests made are: */ /* dropWriteAccess => cannot re-gain write access once permanently dropped.*/ /* sessionCheckUserName() => checks wspec/passwd.wrm for user. */ /* setWriteLock() => fails if we can't set a write lock, MUST do this */ /* before doing sessionDatabaseWritable() otherwise */ /* there is a window where another user can screw up */ /* file permissions and we don't see this. */ /* sessionDatabaseWritable() => check we can write files into database */ /* subdir and that blockN.wrm files are R/W */ if (dropWriteAccess || !sessionCheckUserName(TRUE) || !(write_lock = setWriteLock()) /* Remember if we got a lock. */ || !sessionDatabaseWritable(TRUE)) { if (debug) { messdump ("gainWriteAccess Failed") ; if (dropWriteAccess) messdump ("dropWriteAcces=TRUE") ; if (!sessionDatabaseWritable(TRUE)) messdump ("sessionDatabaseWritable=FALSE") ; if (!sessionCheckUserName(TRUE)) messdump ("sessionCheckUserName=FALSE") ; } if (write_lock) releaseWriteLock() ; return FALSE; } /* Cannot gain write access in an older session */ if (!sessionCheckSuperBlock()) { messout ("Sorry, while you where reading the database, " "another process updated it.\n" "// You can go on reading, but to write, you must " "quit and rerun.") ; if (write_lock) releaseWriteLock(); return FALSE; } /* all checks passed */ writeAccess = TRUE ; /* was false before */ /* therefore calls the routine that gets notified of the change, this is usually to re-draw buttons or change the menus, etc */ if (writeAccessChangeRoutine) (*writeAccessChangeRoutine)(); return TRUE; } /* sessionGainWriteAccess */ /*************************************************************/ void sessionReleaseWriteAccess (void) /* general public function */ { if (isWriteAccess()) { releaseWriteLock () ; writeAccess = FALSE ; if (writeAccessChangeRoutine) (*writeAccessChangeRoutine)(); } return; } /* sessionReleaseWriteAccess */ /*************************************************************/ static BOOL sessionCheckUserName (BOOL doTalk) { #if defined(MACINTOSH) extern BOOL passPrompt ( void ) ; if(!passPrompt()) { messout ("Sorry, your name was not found in the password file") ; return FALSE ; } else return TRUE ; #else /* !MACINTOSH */ static int result = 0 ; register char *cp, *name ; Stack uStack ; ACEIN passwd_io = 0 ; char *passwd_filename; /* check only once, since the userid of the process cannot change */ switch (result) { case 0: result = 1 ; break ; case 1: return FALSE ; case 2: return TRUE ; } #if defined (__CYGWIN__) if ( GetVersion() & 0x80000000) /* For windows 95, 98, always return TRUE, since it has no security */ { result = 2; return TRUE; } #endif /* Only users listed in wspec/passwd.wrm can get write access */ /* I cannot use cuserid, because I want the real userid and, depending on systems, cuserid may return either the real or the effective userid which is probably reset by setuid(). On the other hand, Unix getlogin() is replaced by local getLogin() because getlogin() returns NULL if the process is not attached to a terminal */ name = getLogin(TRUE) ; if (debug) { char *fname = dbPathMakeFilName("wspec","passwd","wrm",0); messdump ("getLogin=%s",name?name:"name=0") ; messdump ("wspec=%s",fname) ; messfree(fname); } passwd_filename = dbPathMakeFilName("wspec", "passwd", "wrm", 0); passwd_io = aceInCreateFromFile(passwd_filename, "r", "", 0); if (!passwd_io) { messout ("The database has no file wspec/passwd.wrm. To enable " "write access to the database, the usernames " "of trusted administrators have to be listed in the " "file : %s", passwd_filename); messfree(passwd_filename); return FALSE ; } messfree(passwd_filename); uStack = stackCreate(32); while (aceInCard (passwd_io)) { aceInNext (passwd_io) ; if (!aceInStep(passwd_io,'#')) /* skip # lines */ if ((cp = aceInWord(passwd_io))) /* read user name */ pushText(uStack,cp) ; } /* Warn user that NOLOCK is no longer valid in wspec/passwd.wrm */ #ifdef ACEDB5_forget_taht_mess /* within acedb4, this creates incompatibility with acedb.4_7 code */ stackCursor(uStack, 0) ; while (!stackAtEnd (uStack)) if (strcmp ("NOLOCK", stackNextText(uStack)) == 0) { messExit("The \"NOLOCK\" option has been found in wspec/passwd.wrm, " "this option must now be set in wspec/database.wrm. " "You should ask your database administrator to move this option " "from wspec/passwd.wrm to wspec/database.wrm and then you will " "be able to use the database. (This session will now terminate " "because running with \"NOLOCK\" set incorrectly may cause " "database integrity problems.)") ; } #endif /* find our user-name amongst names in passwd.wrm */ stackCursor(uStack, 0) ; while (!stackAtEnd (uStack)) if (strcmp (name, stackNextText(uStack)) == 0) { stackDestroy (uStack) ; aceInDestroy (passwd_io) ; result = 2 ; return TRUE ; /* found it */ } /* failed to find the name */ if (doTalk) { if (strcmp (name, "getLoginFailed") == 0) messout ("On your particular machine, the operating system call " "\"getlogin\" failed. To circumvent this problem, you " "may uncomment the user name \"getLoginFailed\" in " "the file wspec/passwd.wrm.\n" "But from then on, everybody will have write access " "unless you set up other security measures using " "write permissions.") ; else messout ("%s is not a registered user. " "To register a user for write access add their " "login name to the file wspec/passwd.wrm", name) ; } stackDestroy(uStack) ; aceInDestroy (passwd_io) ; return FALSE ; #endif /* !MACINTOSH */ } /*************************************************************/ static BOOL sessionDatabaseWritable(BOOL doTalk) { BOOL result = FALSE ; FILE *f; char *name = NULL ; /* first see if we can create a file in the database directory. */ name = dbPathMakeFilName("database", "test", 0, 0); seteuid(euid); unlink(name); f = fopen (name, "a"); if (f) fclose(f); unlink(name); seteuid(ruid); messfree(name); if (!f) { if (doTalk) { name = dbPathMakeFilName("database", 0, 0, 0); messout ("Sorry, the program does not have permission to write " "files in %s", name) ; messfree(name); } result = FALSE ; } else { /* OK, now check the blockN.wrm files. */ result = blocksWriteable(doTalk) ; } return result ; } /* sessionDatabaseWritable */ /* This code checks that all the blockN.wrm files specified in */ /* wspec/database.wrm that actually exist in database/ are readable and */ /* writeable. This check was introduced because previously users could get */ /* write access without actually having write access to some block files. */ /* Then when they tried to save, the database would get corrupted if they */ /* needed to write to those files. */ /* If report_errors is TRUE, errors are logged and an error message output, */ /* otherwise the errors are just logged. */ /* */ static BOOL blocksWriteable(BOOL report_errors) { BOOL result ; char *config_file ; ACEIN config_in; char *cp ; seteuid(euid); /* If we can't find wspec/database.wrm then something is very wrong. */ if (!(config_file = dbPathStrictFilName("wspec", "database", "wrm", "r", 0))) messcrash("Cannot find database configuration file wspec/database.wrm") ; config_in = aceInCreateFromFile (config_file, "r", "", 0); messfree(config_file); /* Look for all the block records which have the format: */ /* PART : 1 local ACEDB block1.wrm 1000 0 */ result = TRUE ; while (aceInCard (config_in)) { char *block_name = NULL ; if (!(cp = aceInWord(config_in))) continue ; /* skip empty lines and comments */ if (strcmp(cp, "PART") != 0) /* only do lines with PART keyword */ continue ; /* Fourth word is blocks file name. */ aceInStep (config_in, ':') ; if (!(cp = aceInWord (config_in)) || !(cp = aceInWord (config_in)) || !(cp = aceInWord (config_in)) || !(cp = aceInWord (config_in))) messExit("Parse error in database definition file %s at line %d", dbPathMakeFilName("wspec", "database", "wrm", 0), (aceInStreamLine(config_in) - 1)) ; /* If the file exists, check to see if it is readable/writeable. */ block_name = dbPathMakeFilName("database", cp, NULL, 0) ; if (access(block_name, F_OK) == 0) /* No equivalent in filsubs.c ? */ { BOOL error = FALSE ; char *access = NULL ; if (!filCheckName(block_name, NULL, "r")) { result = FALSE ; error = TRUE ; access = "read" ; } else if (!filCheckName(block_name, NULL, "w")) { result = FALSE ; error = TRUE ; access = "write" ; } if (error) { if (report_errors) messerror("Cannot get %s access to database block file %s", access, block_name) ; else messdump("Cannot get %s access to database block file %s", access, block_name) ; } } messfree(block_name) ; } aceInDestroy (config_in); seteuid(ruid); return result ; } /*************************************************************/ /* If another process has written since I started either he has grabed the lock, and I can't write, or he may have released it but then he may have updated the disk. In this case, the session number on block 1 will be modified and I will know that my BAT is out of date */ BOOL sessionCheckSuperBlock(void) { int nn = 0 ; #ifndef ACEDB5 BLOCK newSuperBlock ; #endif /* The call to diskFlushLocalCache forces the latest version of the superblock to be read from the NFS server (for remote disks). This closes a concurrency hole which can see this function succeed because it is checking a stale, client side cached copy of the superblock. */ diskFlushLocalCache(); #ifdef ACEDB5 nn = aDiskGetGlobalAdress() ; #else /* !ACEDB5 */ diskblockread (&newSuperBlock,1) ; if (swapData) swapSuperBlock(&newSuperBlock); nn = newSuperBlock.gAddress ; if (thisSession.gAddress != nn) { int ourSession = theSuperBlock.h.session, diskSession = newSuperBlock.h.session; /* keep a record of this occurence in the logfile */ messdump("FATAL ERROR cached superblock inconstistent " "with disk : ourSession=%d diskSession=%d", ourSession, diskSession); return FALSE ; } #endif return TRUE ; } /* sessionCheckSuperBlock */ /*************************************************************/ /* general public function */ void sessionForbidWriteAccess (void) { /* definitive, can't re-gain write access ever again, Should we have a AllowWriteAccess function to match it ?? */ readlockDeleteFile(); /* read locks no longer needed */ dropWriteAccess = TRUE ; /* make it permanent */ /* the permanent loss of write access will change the behaviour of writeAccessPosibble(), and therefor may change buttons or menus */ if (writeAccessChangeRoutine) (*writeAccessChangeRoutine)(); return; } /* sessionForbidWriteAccess */ /*************************************************************/ BOOL writeAccessPossible(void) /* general public function */ { /* return TRUE if it would be possible to gain write access */ static int status = 0; if (dropWriteAccess) return FALSE ; if (status) return status == 1 ? TRUE : FALSE ; if (sessionDatabaseWritable(FALSE) && sessionCheckUserName(FALSE)) { status = 1; return TRUE; } else { status = 2; return FALSE; } } /* writeAccessPossible */ /**************************************************************/ /* Used to turn on/off saving globally, i.e. if you want to completely disable * saving, perhaps for testing purposes, you can use this call to prevent any * database saving. */ void sessionSetSaveStatus(BOOL save) { saveDB_G = save ; return ; } /**************************************************************/ /* Used largely in command.c by commands that need to save a session * after some operation that updates the database. */ /* used also in aceserver.c */ void sessionMark (BOOL doIt, int newTimeOut) { static mytime_t t = 0 , t0 = 0 ; static int dt, timeOut = -1 ; t = timeNow () ; if (!t0) t0 = t ; if (newTimeOut > 0) timeOut = newTimeOut ; if (isWriteAccess()) { if (doIt || (timeDiffSecs (t, t0, &dt) && dt > timeOut)) { messdump("Saving Session %d at %s", thisSession.session, timeShow(timeParse("now"))) ; #ifdef ED_G_NEVER_INCLUDE_THIS_CODE /* A temporary fix, really the whole log control/session close/open * bit needs sorting out. */ sessionClose (TRUE) ; #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ closeSession(TRUE) ; messdump("Saving done %s", timeShow(timeParse("now"))) ; } } t0 = timeNow () ; return; } /* sessionMark */ /**************************************************************/ /* this routine has come to be used as the "exit" routine by acedb * applications so it closes the log. Perhaps that should not be, if * you want to change it then you'll need to properly change everything * in all applications. */ /* Close a database session, with or without saving current changes. */ void sessionClose (BOOL doSave) { closeSession(doSave) ; acedbCloseLog(ACELOG_NORMAL_EXIT) ; return ; } /* close and reinitialise a session, used for instance to save the database * periodically while an application is making updates. * Note that saving can be completely turned off, good for testing but * not for the faint hearted. */ static void closeSession(BOOL doSave) { /* Only save if the user has not turned off saving altogether. */ if (saveDB_G) { sessionAutoSave(-3, 0) ; /* synchronizes autosave, without activating it */ if (doSave == FALSE) { sessionReleaseWriteAccess() ; } else { if (sessionDoClose ()) /* returns TRUE if something was saved */ sessionInitialize () ; /* only init if we had saved */ } sessionAutoSave (-4, 0) ; /* synchronizes autosave, without activating it */ } else { /* Some tidying up to do even we are not saving. */ sessionReleaseWriteAccess() ; } return ; } /*****************************************************************/ /*****************************************************************/ /******* AutoSaving package, mieg: march 2000 ********************/ /*****************************************************************/ /* Saves if more than timeLapse seconds elapsed since last save * or since timeZero if set * returns time since last save */ unsigned int sessionTimeSave (unsigned int timeLapse, int mode) { int dt = 0 ; mytime_t now = timeNow () ; BOOL debug = TRUE ; static mytime_t timeZero = 0 ; switch (mode) { case 2: timeZero = now ; break ; case 1: timeZero = 0 ; break ; case 0: if (timeZero) timeDiffSecs (timeZero, now, &dt) ; else timeDiffSecs (thisSessionStartTime, now, &dt) ; if (isWriteAccess() && (dt + 10 > timeLapse)) /* avoid rounding problems, and never substract on unsigned int */ { messStatus ("Saving"); messdump("Autosaving if %d s elapsed done, last save was %d s ago", timeLapse, dt) ; timeZero = 0 ; sessionClose (TRUE) ; sessionGainWriteAccess () ; /* grab again */ now = timeNow () ; messdump("Autosaving done") ; timeDiffSecs (thisSessionStartTime, now, &dt) ; } else if (debug) messdump("Autosaving if %d s elapsed not needed, last %d s ago ", timeLapse, dt) ; break ; default: messcrash ("Wrong call to sessionTimeSave, mode = %d", mode) ; break ; } return dt ; } /*************************************************************/ /* alarm routine */ static void sessionAutoSaveAlarm (int dummy) { unsigned int dt = 0, /* time since last save */ dtAlarm = 0, /* delay till next alarm */ s1 = sessionAutoSave (-1, 0) , /* in seconds */ s2 = 0 ; /* ask value after saving */ BOOL debug = TRUE ; ; alarm ((unsigned int) 0) ; /* disables autosaving */ if (s1 > 0) { if (debug) messdump("AutoSaveAlarm calling sessionTimeSave (%d,0)", s1, 0) ; dt = sessionTimeSave (s1, 0) ; /* save if necessary */ s2 = sessionAutoSave (-2, 0) ; if (s2 > 0) { dtAlarm = s2 > dt ? s2 - dt : s2 ; if (dtAlarm < 10) dtAlarm = 10 ; signal (SIGALRM, sessionAutoSaveAlarm) ; /* self register */ alarm (dtAlarm) ; /* raise alarm */ if (debug) messdump ("Autosaving alarm expected in %d s", dtAlarm) ; } else if (debug) messdump ("Autosaving alarm cancelled", dtAlarm) ; } else if (debug) messdump ("Autosaving alarm cancelled", dtAlarm) ; return ; } /*************************************************************/ /* * sessionAutoSave (int saveInterval, int inactivityInterval) * * Example * From anywhere in the code call * sessionAutoSave (1800, 300) * As a result the system will autosave every half hour * or at the end of the first 5 minutes of innactivity * or of course on explicit user request * ************** Public calls * saveInterval > 0 : The maximal delay before a save * saveInterval = 0 : cancels * inactivityInterval : The maximal inactivity delay before a save * Activity is raised by the acedb kernel calls to bsCreate ************** Kernel calls * negative values are private to sessionAutoSave/acedb-kernel *** saveInterval = -1 : returns maximal saveTime interval *** saveInterval = -2 : returns delay till next alarm *** saveInterval = -3 : ace kernel starts saving *** saveInterval = -4 : ace kernel finished saving *** saveInterval = -8 : signals modif, called by bsUpdate *** saveInterval = -9 : signals activity, called by bsCreate ************** State *** 0: unset *** 1: saving while unset *** 2: saving while set *** 3: dead *** 4: inactif *** 5: actif ************* Algorithm * This routine is just a finite state machine, actual job done by * sessionAutoSaveAlarm which self registers in alarm() and calls * sessionTimeSave which saves if last save too old */ unsigned int sessionAutoSave (int saveInterval, int inactivityInterval) { static int state = 0, longDelay = 0 , shortDelay = 0 ; int noDelay = 1 ; BOOL debug = TRUE ; if (getenv ("ACEDB_NO_AUTOSAVE")) return 0 ; if (saveInterval == -9) /* record activity, called by all bsCreate */ switch (state) { case 0: /* autoSave not set, ignore */ case 2: /* saving while set , ignore */ case 1: /* saving while unset, ignore */ return 0 ; case 3: /* dead, ignore */ return 3 ; case 4: /* inactif */ state = 5 ; /* new activity occured */ case 5: /* actif */ return 5 ; /* return actif */ default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval == -8) /* record modif, called by all bsUpdate */ switch (state) { case 0: /* autoSave not set, ignore */ case 2: /* saving while set , ignore */ case 1: /* saving while unset, ignore */ return 0 ; case 3: /* raise from dead */ if (debug) messdump("sessionAutoSave(-8) calling sessionTimeSave (0,2)") ; sessionTimeSave (0, 2) ; /* synchronize */ sessionAutoSaveAlarm (0) ; case 4: /* inactif */ state = 5 ; /* new activity occured */ case 5: /* actif */ return 5 ; /* return actif */ default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval == -1) /* gives maximal delay since previous save */ switch (state) { case 0: /* autoSave not set */ case 1: /* saving while unset, ignore */ case 2: /* saving while set, ignore */ case 3: /* dead, goto sleep */ return 0 ; case 4: /* inactif, save immediately */ return noDelay ; case 5: /* actif */ state = 4 ; /* sets to inactif, crucial switch of whole system */ return longDelay ; default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval == -2) /* gives delay till next alarm */ switch (state) { case 0: /* autoSave not set */ case 1: /* saving while unset, ignore */ case 2: /* saving while set, ignore */ case 3: /* dead, go to sleep */ return 0 ; case 4: /* inactif */ case 5: /* actif */ return shortDelay ; default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval == -3) /* saving starts */ switch (state) { case 0: /* autoSave not set */ state = 1 ; /* set saving while unset */ case 1: /* saving while unset, ignore */ return 1 ; case 2: /* saving while set, ignore */ case 3: /* dead */ case 4: /* inactif */ case 5: /* actif */ state = 2 ; return 2 ; default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval == -4) /* saving ends */ switch (state) { case 0: /* autoSave not set */ return 0 ; case 1: /* saving while unset, revert to unset */ state = 0 ; if (debug) messdump("sessionAutoSave(-4) calling sessionTimeSave (0,2)") ; sessionTimeSave (0, 2) ; /* synchronize */ return 0 ; case 2: /* saving while set, revert to dead */ case 3: /* dead */ case 4: /* inactif */ state = 3 ; return 3 ; case 5: /* actif */ state = 3 ; if (debug) messdump("sessionAutoSave(-5) calling sessionTimeSave (0,2)") ; sessionTimeSave (0, 2) ; /* synchronize */ sessionAutoSaveAlarm (0) ; return 3 ; default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } if (saveInterval <= 0) /* zero or silly values, disables autosave */ switch (state) { case 0: /* autoSave not set, ignore */ case 1: /* saving while unset ignore */ return 0 ; case 2: /* saving while set, unset with delay */ state = 1 ; longDelay = shortDelay = 0 ; return 1 ; case 3: /* autosave set, unset */ case 4: case 5: state = 0 ; longDelay = shortDelay = 0 ; sessionAutoSaveAlarm (0) ; return 1 ; default: messcrash ("sessionAutoSave in wrong state %d", state) ; return state ; } /* verify parameters are reasonable */ if (saveInterval <= 3 * 60) saveInterval = 180 ; /* 300 ; */ if (inactivityInterval < 60) inactivityInterval = 60 ; if (shortDelay != inactivityInterval || longDelay != saveInterval) state = 0 ; /* reregistering needed */ if (state != 5) { state = 5 ; /* short wait needed */ shortDelay = inactivityInterval ; longDelay = saveInterval ; sessionAutoSaveAlarm (0) ; /* register */ } return state ; } /*****************************************************************/ /********** AutoSaving package end *******************************/ /*****************************************************************/ void sessionDoSave (BOOL reGainWriteAccess) /* general public function */ { /* saves even if i had forgotten to take write access before, there can be no conflict, because if another user has written i won't be able to grab the write access */ messStatus ("Saving") ; if (!isWriteAccess()) sessionGainWriteAccess () ; /* try to grab it */ if (isWriteAccess()) { sessionClose (TRUE) ; if (reGainWriteAccess) sessionGainWriteAccess () ; /* grab again */ } } /*************************************************************/ BOOL sessionSetUserSessionUser(char *user) { BOOL result ; int i ; if (userSessionUser) messcrash("User session user set twice"); for (i = 0, result = TRUE ; (user[i]) ; i++) { if (!isalnum((int)(user[i])) && user[i] != '_') { result = FALSE ; break ; } } if (result) userSessionUser = strnew(user, 0); return result ; } KEY sessionUserKey (void) { mytime_t savedTime; /* Return zero when thisSessionStartTime is zero. This is important in two cases. a) Before a userSession has started, when eg timestamps get put on objects in some system classes. b) To avoid infinite recursion when trying to timestamp timestamps. These get zero timestamps because thisSessionStartTime is temporarily set to zero before calling lexaddkey which recursively calls sessionUserKey */ if (!thisSessionStartTime) return 0; if (!thisSession.userKey) { /* This avoids inifinite recursion. The timestamps for the system objects created in recusive calls to sessionUserKey during the lexaddkey below will be zero. (because sessionUserKey always returns zero when thisSessionSTartTime is zero. This is OK. */ savedTime = thisSessionStartTime; thisSessionStartTime = 0; if (!userSessionUser) sessionSetUserSessionUser(getLogin(TRUE)); if (!lexaddkey (messprintf ("%s_%s", timeShow(savedTime), userSessionUser), &thisSession.userKey, _VUserSession)) { /* we've had that key already, now make a unique one */ int i = 0 ; while (!lexaddkey (messprintf ("%s.%d_%s", timeShow (savedTime), ++i, userSessionUser), &thisSession.userKey, _VUserSession)) ; } thisSessionStartTime = savedTime; } return thisSession.userKey ; } mytime_t sessionStartTime(void) { return thisSessionStartTime; } /*************************************************************/ /********************* Readlock manager **********************/ /*************************************************************/ static char readlock_filename[MAXPATHLEN] = ""; void sessionCheckReadlock (void) /* public function called periodically to make sure that nobody else has destroyed our readlock. This may happen if a process is legitimately running for a long time (longer than readlockTimeout) and the readlocks was killed due to timeout by another process */ { struct stat status ; if (strlen(readlock_filename) == 0) /* don't use readlocks */ return; if (stat (readlock_filename, &status) != 0) /* failed to probe the file */ readlockCreateFile(); return; } /* sessionCheckReadlock */ /*************************************************************/ /* creates the directory database/readlocks/ and gives everybody write access to it. This directory will contain lock files, that specify which sessions are being looked at, so they aren't destroyed under an old process' feet */ /*************************************************************/ static BOOL readlockCreateDir (void) { static int readlock_status = 0; /* 0 : not tried before 1 : everything OK 2 : had problems, don't try again */ char *readlock_dir; STORE_HANDLE handle = handleCreate();; if (readlock_status != 0) return (readlock_status == 1 ? TRUE : FALSE); if (dbPathStrictFilName("database", "readlocks", 0, "wd", handle)) { /* a writable directory already exists */ readlock_status = 1; /* no need to check again next time */ handleDestroy(handle); return TRUE; } readlock_dir = dbPathMakeFilName("database", "readlocks", 0, handle); /* make the readlocks directory with rwxrwxrwx permissions, so that any other user can create and remove files from there */ if (mkdir (readlock_dir, 0000777) == -1) { /* unsuccessful completion of command */ /* This will happen, if we us this code on a database, which the administrator (the one with all the permissions) hasn't initialised yet - that would have created the directory correctly, even for users to use, that normally only have read-only permissions. */ /* no big error-message, we'll have to silently accept it */ readlock_status = 2; /* don't try (and fail) again */ handleDestroy (handle); return FALSE; } /* I noticed that the mkdir(2) won't assign the right permissions at all times. That may have to do with different umask(2) settings. So we explicitly change the permissions again. The chmod fails for very mysterious reasons under windows, so we skip this on that platfrom. */ #ifndef __CYGWIN__ if (chmod (readlock_dir, 0777) == -1) { readlock_status = 2; /* don't try (and fail) again */ handleDestroy (handle); return FALSE; } #endif readlock_status = 1; /* everything went OK */ messdump ("Readlock manager : created readlocks directory %s", readlock_dir); handleDestroy (handle); return TRUE; /* managed to create directory alright */ } /* readLockCreateDir */ /*************************************************************/ /* for the given session, create the readlock file the file-name has the format .. and the file is in the directory database/readlocks/... */ /*************************************************************/ static BOOL readlockCreateFile (void) { char host_name[100]; int pid; int fd; char *buffer; STORE_HANDLE handle = handleCreate(); if (dropWriteAccess) return TRUE; if (!(readlockCreateDir())) return FALSE; gethostname (host_name, 100) ; pid = getpid () ; sprintf(readlock_filename, "%s/%d.%s.%d", dbPathStrictFilName("database", "readlocks", 0, "wd", handle), thisSession.session, host_name, pid); /* create the readlockfile with rw-rw-rw- permission. */ /* That makes it possible for other processes to remove expired readlock files that this proces may leave behind */ fd = open(readlock_filename, O_RDWR | O_CREAT | O_SYNC, 0666); if (fd == -1) { fprintf (stderr, "Cannot create readlock file %s (%s)", readlock_filename, messSysErrorText()); strcpy (readlock_filename, ""); handleDestroy(handle); return FALSE; } buffer = messprintf ("Readlock file to prevent destruction of " "sessions still in use by other processes\n" "Created: %s\n" "User: %s\n" "Program: %s\n" "Version: %s\n" "Linkdate: %s\n", timeShow(timeNow()), getLogin(TRUE), messGetErrorProgram(), aceGetVersionString(), aceGetLinkDateString()); write (fd, buffer, strlen(buffer)); close (fd); /* I noticed that the open(2) won't assign the right permissions at all times. That may have to do with different umask(2) settings. So we explicitly change the permissions again. */ if (chmod (readlock_filename, 0666) == -1) { fprintf (stderr, "Cannot chmod readlock file %s (%s)", readlock_filename, messSysErrorText()); strcpy (readlock_filename, ""); return FALSE; } if (debug) messdump ("Readlock manager : start readlock for session %d by %s", thisSession.session, getLogin(TRUE)); handleDestroy(handle); return TRUE; } /* readLockCreateFile */ /* The window is is not known when the readlock file is created, so we append it here */ void sessionReadlockAppendFileWinID(unsigned long id) { FILE *f = fopen(readlock_filename, "a"); if (f) { fprintf(f, "WindowID: 0x%lx\n", id); fclose(f); } } /*************************************************************/ /* when you save a session, we get a new session number (in thisSession.session) so we rename the current readlock-file to contain the new session-number in the file name */ /*************************************************************/ static BOOL readlockUpdateFile (void) { char host_name[100]; int pid; char new_filename[MAXPATHLEN] = ""; STORE_HANDLE handle = handleCreate(); if (dropWriteAccess) return TRUE; if (strlen(readlock_filename) == 0) return FALSE; gethostname (host_name, 100) ; pid = getpid () ; sprintf(new_filename, "%s/%d.%s.%d", dbPathStrictFilName("database", "readlocks", 0, "rd", handle), thisSession.session, host_name, pid); if (rename (readlock_filename, new_filename) == -1) { if (debug) messerror ("failed to rename readlock file %s to %s (%s)", readlock_filename, new_filename, messSysErrorText()); /* don't know how we lost it, but we must have a new readlock */ handleDestroy(handle); return readlockCreateFile(); } strcpy (readlock_filename, new_filename); if (debug) messdump ("Readlock manager : update readlock from session %d to %d", thisSession.session-1, thisSession.session); handleDestroy(handle); return TRUE; } /* readlockUpdateFile */ /*************************************************************/ /* called when the process exists */ /*************************************************************/ BOOL readlockDeleteFile (void) { if (dropWriteAccess) return TRUE; if (strlen(readlock_filename) == 0) return TRUE; /* OK if we haven't got a file */ if (unlink(readlock_filename) == -1) { if (debug) messerror ("failed to remove readlock file %s (%s)", readlock_filename, messSysErrorText()); strcpy (readlock_filename, ""); return FALSE; } strcpy (readlock_filename, ""); if (debug) messdump ("Readlock manager : end readlock for session %d", thisSession.session); return TRUE; } /* readlockDeleteFile */ /*************************************************************/ /* We will create an array of session numbers that still have a valid readlock. Sessions with those numbers will then be treated with caution, because other processes are still looking at that data of those older sessions. */ /*************************************************************/ static Array readlockGetSessionNumbers (BOOL isReport) /* isReport == TRUE, output report about readlocked sessions * on parent window * isReport == FALSE, do output */ { Array readlockedSessions; FilDir dirList = 0; char lockfile_name[MAXPATHLEN]; char *cp1, *cp2, *cp3; char *dirEntry; int file_session_num; char file_host_name[100]; int file_pid; int n, hourAge; char *dir_name; STORE_HANDLE handle = handleCreate(); if (!(readlockCreateDir())) { handleDestroy(handle); return 0; /* no readlocks being used */ } dir_name = dbPathStrictFilName("database", "readlocks", 0, "rd", handle); if (dir_name) dirList = filDirCreate (dir_name, 0, "r"); if (!dirList) { /* this shouldn't really go wrong, because we've managed to open or create the directory already */ messerror ("failed to open directory database/readlocks/ (%s)", messSysErrorText()); return 0; } if (filDirMax(dirList) == 0) /* nothing is readlocked */ return 0; readlockedSessions = arrayCreate (filDirMax(dirList), int); for (n = 0; n < filDirMax(dirList); n++) /* start at 1 to skip . entry */ { dirEntry = filDirEntry(dirList, n); sprintf(lockfile_name, "%s/%s", dbPathStrictFilName("database", "readlocks", 0, "rd", handle), dirEntry); /* don't consider the readlock file of this process */ if (strcmp (lockfile_name, readlock_filename) == 0) continue; /* extract the .. parts from filename */ cp1 = dirEntry; /* find the first dot in the name */ if (!(cp2 = strstr(cp1, "."))) continue; *cp2 = 0; /* cp1 now ends at the dot */ ++cp2; /* cp2 starts just after it */ /* find the first dot from the end */ cp3 = cp2 + strlen(cp2); while (cp3 > cp2 && *cp3 != '.') --cp3; if (cp3 == cp2) continue; *cp3 = 0; /* cp2 now ends at the 2nd dot */ ++cp3; /* cp3 starts just after */ if (!*cp1 || !*cp2 || !*cp3) continue; if (sscanf (cp1, "%d", &file_session_num) != 1) continue; if (sscanf (cp2, "%s", file_host_name) != 1) continue; if (sscanf (cp3, "%d", &file_pid) != 1) continue; hourAge = 0; if (!(filAge (lockfile_name, "", 0, 0, 0, &hourAge, 0, 0))) continue; /* couldn't get age, don't consider it a valid readlock then */ if (hourAge < readlockTimeout) /* still locked then */ { int max = arrayMax(readlockedSessions); /* get session-number from file-name */ /* terminate the filename at the position of the dot then read a number from that first part of the string */ array(readlockedSessions, max, int) = file_session_num; if (isReport) { ACEIN readlock_io; char *readlockCreateTime = "(unknown time)"; char *readlockOwner = "(unknown user)"; char *readlockProgram = "(unknown program)"; char *word; readlock_io = aceInCreateFromFile (lockfile_name, "r", "", handle); if (readlock_io) { while (aceInCard (readlock_io)) { if ((word = aceInWord(readlock_io))) { if (strcmp (word, "Created:") == 0 && (word = aceInWord(readlock_io))) readlockCreateTime = strnew (word, handle); else if (strcmp (word, "User:") == 0 && (word = aceInWord(readlock_io))) readlockOwner = strnew (word, handle); else if (strcmp (word, "Program:") == 0 && (word = aceInWord(readlock_io))) readlockProgram = strnew (word, handle); } } } /* NOTE, the session number that is actually protected * is file_session_num-1, because that's the session * the process was initialised from. */ printf ("// Session %d is " "readlocked by %s since %s using %s " "(process %d on machine %s)\n", file_session_num-1, readlockOwner, readlockCreateTime, readlockProgram, file_pid, file_host_name); } } else /* lock has expired */ { if (unlink (lockfile_name) == 0) { /* successfully removed */ if (debug) messdump ("Readlock manager : kill readlock for session %d, " " process %d on %s is older than %d hours", file_session_num, file_pid, file_host_name, readlockTimeout); } } } messfree(dirList); handleDestroy(handle); return readlockedSessions; } /* readlockGetSessionNumbers */ /******************************************************************/ void sessionSetforceInit(BOOL value) { forceInit_G = value; return; } /******************************************************************/ /*********************** eof **************************************/