diff --git a/src/FileDialog.c b/src/FileDialog.c index 6a7f2435b9..27e4475483 100644 --- a/src/FileDialog.c +++ b/src/FileDialog.c @@ -37,78 +37,86 @@ #include "file-history.h" /* This static indicates the debugging module that this .o belongs to. */ -// static short module = MOD_GUI; +/* static short module = MOD_GUI; */ /** GLOBALS *********************************************************/ static Session *current_session = NULL; static AccountGroup *topgroup = NULL; /* the current top of the hierarchy */ -/********************************************************************\ - * fileMenubarCB -- handles file menubar choices * -\********************************************************************/ -#define SHOW_IO_ERR_MSG(io_error) { \ - switch (io_error) { \ - case ERR_FILEIO_NO_ERROR: \ - break; \ - case ERR_FILEIO_FILE_NOT_FOUND: \ - sprintf (buf, FILE_NOT_FOUND_MSG, newfile); \ - gnc_error_dialog (buf); \ - uh_oh = 1; \ - break; \ - case ERR_FILEIO_FILE_EMPTY: \ - sprintf (buf, FILE_EMPTY_MSG, newfile); \ - gnc_error_dialog (buf); \ - uh_oh = 1; \ - break; \ - case ERR_FILEIO_FILE_TOO_NEW: \ - gnc_error_dialog ( FILE_TOO_NEW_MSG); \ - uh_oh = 1; \ - break; \ - case ERR_FILEIO_FILE_TOO_OLD: \ - if (!gnc_verify_dialog( FILE_TOO_OLD_MSG, TRUE )) { \ - xaccFreeAccountGroup (newgrp); \ - newgrp = NULL; \ - uh_oh = 1; \ - } \ - break; \ - case ERR_FILEIO_FILE_BAD_READ: \ - if (!gnc_verify_dialog( FILE_BAD_READ_MSG, TRUE )) { \ - xaccFreeAccountGroup (newgrp); \ - newgrp = NULL; \ - uh_oh = 1; \ - } \ - break; \ - default: \ - break; \ - } \ +/* ======================================================== */ + +static gboolean +show_file_error (int io_error, char *newfile) +{ + gboolean uh_oh = FALSE; + char *buf = NULL; + + switch (io_error) + { + case ERR_FILEIO_NO_ERROR: + break; + case ERR_FILEIO_FILE_NOT_FOUND: + buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile); + gnc_error_dialog (buf); + uh_oh = TRUE; + break; + case ERR_FILEIO_FILE_EMPTY: + buf = g_strdup_printf (FILE_EMPTY_MSG, newfile); + gnc_error_dialog (buf); + uh_oh = TRUE; + break; + case ERR_FILEIO_FILE_TOO_NEW: + gnc_error_dialog (FILE_TOO_NEW_MSG); + uh_oh = TRUE; + break; + case ERR_FILEIO_FILE_TOO_OLD: + if (!gnc_verify_dialog (FILE_TOO_OLD_MSG, TRUE)) + uh_oh = TRUE; + break; + case ERR_FILEIO_FILE_BAD_READ: + if (!gnc_verify_dialog (FILE_BAD_READ_MSG, TRUE)) + uh_oh = TRUE; + break; + default: + break; + } + + g_free (buf); + + return uh_oh; } - -#define SHOW_LOCK_ERR_MSG(session) \ - { \ - int norr = xaccSessionGetError (session); \ - if (ETXTBSY == norr) \ - { \ - sprintf (buf, FMB_LOCKED_MSG, newfile); \ - gnc_error_dialog (buf); \ - uh_oh = 1; \ - } \ - else \ - if (ERANGE == norr) \ - { \ - sprintf (buf, FILE_NOT_FOUND_MSG, newfile); \ - gnc_error_dialog (buf); \ - uh_oh = 1; \ - } \ - else \ - if (norr) \ - { \ - sprintf (buf, FMB_INVALID_MSG, newfile); \ - gnc_error_dialog (buf); \ - uh_oh = 1; \ - } \ - } \ +/* ======================================================== */ + +static gboolean +show_session_error(Session *session, char *newfile) +{ + int norr = xaccSessionGetError (session); + gboolean uh_oh = FALSE; + char *buf = NULL; + + if (ETXTBSY == norr) + { + uh_oh = TRUE; + } + else if (ERANGE == norr) + { + buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile); + gnc_error_dialog (buf); + uh_oh = TRUE; + } + else if (norr) + { + buf = (FMB_INVALID_MSG, newfile); + gnc_error_dialog (buf); + uh_oh = TRUE; + } + + g_free(buf); + + return uh_oh; +} /* ======================================================== */ @@ -191,6 +199,29 @@ gncFileQuerySave (void) return TRUE; } +/* ======================================================== */ + +static gboolean +gncLockFailHandler (const char *file) +{ + const char *format = _("Gnucash could not obtain the lock for\n" + " %s.\n" + "That file may be in use by another user,\n" + "in which case you should not open the file.\n" + "\nDo you want to proceed with opening the file?"); + char *message; + gboolean result; + + if (file == NULL) + return FALSE; + + message = g_strdup_printf (format, file); + result = gnc_verify_dialog (message, FALSE); + g_free (message); + + return result; +} + /* ======================================================== */ /* private utilities for file open; done in two stages */ @@ -199,16 +230,17 @@ gncPostFileOpen (const char * filename) { Session *newsess; AccountGroup *oldgrp; - int io_error, uh_oh=0; - char buf[BUFSIZE]; + gboolean uh_oh = FALSE; + int io_error; AccountGroup *newgrp; char * newfile; if (!filename) return; newfile = xaccResolveFilePath (filename); if (!newfile) { - sprintf (buf, FILE_NOT_FOUND_MSG, filename); + char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, filename); gnc_error_dialog (buf); + g_free(buf); return; } @@ -227,26 +259,32 @@ gncPostFileOpen (const char * filename) * switchover is not something we want to keep in a journal. */ gnc_set_busy_cursor(NULL); xaccLogDisable(); - newgrp = xaccSessionBeginFile (newsess, newfile); + newgrp = xaccSessionBeginFile (newsess, newfile, gncLockFailHandler); xaccLogEnable(); gnc_unset_busy_cursor(NULL); /* check for session errors, put up appropriate dialog */ - SHOW_LOCK_ERR_MSG (newsess); + uh_oh = show_session_error (newsess, newfile); if (!uh_oh) { /* check for i/o error, put up appropriate error message */ io_error = xaccGetFileIOError(); - SHOW_IO_ERR_MSG(io_error); + uh_oh = show_file_error (io_error, newfile); + if (uh_oh) + { + xaccFreeAccountGroup (newgrp); + newgrp = NULL; + } /* Umm, came up empty-handed, i.e. the file was not found. */ /* This is almost certainly not what the user wanted. */ if (!uh_oh && !newgrp && !io_error) { - sprintf (buf, FILE_NOT_FOUND_MSG, newfile); - gnc_error_dialog ( buf); - uh_oh = 1; + char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, newfile); + gnc_error_dialog (buf); + g_free (buf); + uh_oh = TRUE; } } @@ -258,9 +296,9 @@ gncPostFileOpen (const char * filename) /* well, no matter what, I think its a good idea to have * a topgroup around. For example, early in the gnucash startup - * sequence, the user opens a file ... if this open fails for any + * sequence, the user opens a file; if this open fails for any * reason, we don't want to leave them high & dry without a topgroup, - * because if user continues, then bad things will happen ... + * because if the user continues, then bad things will happen. */ if (NULL == topgroup) { @@ -343,7 +381,6 @@ gncFileSave (void) { AccountGroup *newgrp = NULL; char * newfile; - char buf[BUFSIZE]; int io_error, norr, uh_oh = 0; /* hack alert -- Somehow make sure all in-progress edits get committed! */ @@ -392,7 +429,13 @@ gncFileSave (void) io_error = xaccGetFileIOError(); newfile = xaccSessionGetFilePath(current_session); gnc_history_add_file(newfile); - SHOW_IO_ERR_MSG(io_error); + + uh_oh = show_file_error (io_error, newfile); + if (uh_oh) + { + xaccFreeAccountGroup (newgrp); + newgrp = NULL; + } /* going down -- abandon ship */ if (uh_oh) return; @@ -411,8 +454,8 @@ gncFileSaveAs (void) char *newfile; AccountGroup *newgrp; char * oldfile; - char buf[BUFSIZE]; - int io_error, uh_oh = 0; + int io_error; + gboolean uh_oh = FALSE; filename = fileBox(SAVE_STR, "*.gnc"); if (!filename) return; @@ -424,8 +467,9 @@ gncFileSaveAs (void) */ newfile = xaccResolveFilePath (filename); if (!newfile) { - sprintf (buf, FILE_NOT_FOUND_MSG, filename); + char *buf = g_strdup_printf (FILE_NOT_FOUND_MSG, filename); gnc_error_dialog (buf); + g_free (buf); return; } oldfile = xaccSessionGetFilePath (current_session); @@ -449,17 +493,22 @@ gncFileSaveAs (void) * edit; the mass deletetion of accounts and transactions during * switchover is not something we want to keep in a journal. */ xaccLogDisable(); - newgrp = xaccSessionBeginFile (newsess, newfile); + newgrp = xaccSessionBeginFile (newsess, newfile, gncLockFailHandler); xaccLogEnable(); /* check for session errors (e.g. file locked by another user) */ - SHOW_LOCK_ERR_MSG (newsess); + uh_oh = show_session_error (newsess, newfile); if (!uh_oh) { /* check for i/o error, put up appropriate error message */ io_error = xaccGetFileIOError(); - SHOW_IO_ERR_MSG(io_error); + uh_oh = show_file_error (io_error, newfile); + if (uh_oh) + { + xaccFreeAccountGroup (newgrp); + newgrp = NULL; + } } /* going down -- abandon ship */ @@ -496,10 +545,15 @@ gncFileSaveAs (void) if (newgrp) { char *tmpmsg; - tmpmsg = alloca (strlen (FMB_EEXIST_MSG) + strlen (newfile)); - sprintf (tmpmsg, FMB_EEXIST_MSG, newfile); + gboolean result; + + tmpmsg = g_strdup_printf (FMB_EEXIST_MSG, newfile); + result = gnc_verify_dialog (tmpmsg, FALSE); + g_free (tmpmsg); + /* if user says cancel, we should break out */ - if (! gnc_verify_dialog (tmpmsg, FALSE)) return; + if (!result) + return; /* Whoa-ok. Blow away the previous file. * Do not disable logging ... we want to capture the @@ -537,7 +591,6 @@ gncFileQuit (void) xaccSessionEnd (current_session); xaccSessionDestroy (current_session); current_session = NULL; - // xaccGroupWindowDestroy (grp); xaccFreeAccountGroup (grp); topgroup = NULL; } diff --git a/src/engine/Session.c b/src/engine/Session.c index a2e39f3c67..3e8f9e9038 100644 --- a/src/engine/Session.c +++ b/src/engine/Session.c @@ -1,15 +1,3 @@ -/* - * FILE: - * Session.c - * - * FUNCTION: - * Provide wrappers for initiating/concluding a file-editing session. - * - * HISTORY: - * Created by Linas Vepstas December 1998 - * Copyright (c) 1998-2000 Linas Vepstas - */ - /********************************************************************\ * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -29,6 +17,18 @@ * Boston, MA 02111-1307, USA gnu@gnu.org * \********************************************************************/ +/* + * FILE: + * Session.c + * + * FUNCTION: + * Provide wrappers for initiating/concluding a file-editing session. + * + * HISTORY: + * Created by Linas Vepstas December 1998 + * Copyright (c) 1998-2000 Linas Vepstas + */ + #include #include #include @@ -81,7 +81,7 @@ Session * xaccMallocSession (void) { Session *sess; - sess = (Session *) malloc (sizeof (Session)); + sess = g_new(Session, 1); xaccInitSession (sess); return sess; @@ -179,7 +179,7 @@ xaccSessionBegin (Session *sess, const char * sid) } /* add 5 to space past 'file:' */ - retval = xaccSessionBeginFile (sess, sid+5); + retval = xaccSessionBeginFile (sess, sid+5, NULL); return retval; } @@ -217,12 +217,79 @@ extern Backend * pgendNew (void); /* ============================================================== */ +static gboolean +xaccSessionGetFileLock (Session *sess) +{ + struct stat statbuf; + char pathbuf[PATH_MAX]; + char *path = NULL; + int rc; + + rc = stat (sess->lockfile, &statbuf); + if (!rc) { + /* oops .. file is all locked up .. */ + sess->errtype = ETXTBSY; + return FALSE; + } + + sess->lockfd = open (sess->lockfile, O_RDWR | O_CREAT | O_EXCL , 0); + if (0 > sess->lockfd) { + /* oops .. file is all locked up .. */ + sess->errtype = ETXTBSY; + return FALSE; + } + + /* OK, now work around some NFS atomic lock race condition + * mumbo-jumbo. We do this by linking a unique file, and + * then examing the link count. At least that's what the + * NFS programmers guide suggests. + * Note: the "unique filename" must be unique for the + * triplet filename-host-process, otherwise accidental + * aliases can occur. + */ + + /* apparently, even this code may not work for some NFS + * implementations. In the long run, I am told that + * ftp.debian.org + * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz + * provides a better long-term solution. + */ + + strcpy (pathbuf, sess->lockfile); + path = strrchr (pathbuf, '.'); + sprintf (path, ".%lx.%d.LNK", gethostid(), getpid()); + link (sess->lockfile, pathbuf); + rc = stat (sess->lockfile, &statbuf); + if (rc) { + /* oops .. stat failed! This can't happen! */ + sess->errtype = ETXTBSY; + unlink (pathbuf); + close (sess->lockfd); + unlink (sess->lockfile); + return FALSE; + } + + if (2 != statbuf.st_nlink) { + /* oops .. stat failed! This can't happen! */ + sess->errtype = ETXTBSY; + unlink (pathbuf); + close (sess->lockfd); + unlink (sess->lockfile); + return FALSE; + } + + sess->linkfile = g_strdup (pathbuf); + + return TRUE; +} + +/* ============================================================== */ + AccountGroup * -xaccSessionBeginFile (Session *sess, const char * filefrag) +xaccSessionBeginFile (Session *sess, const char * filefrag, + SessionLockFailHandler handler) { struct stat statbuf; - char pathbuf[PATH_MAX]; - char *path = NULL; int rc; if (!sess) return NULL; @@ -252,87 +319,27 @@ xaccSessionBeginFile (Session *sess, const char * filefrag) } /* Store the sessionid URL also ... */ - strcpy (pathbuf, "file:"); - strcat (pathbuf, filefrag); - sess->sessionid = strdup (pathbuf); + sess->sessionid = g_strconcat ("file:", filefrag, NULL); /* ---------------------------------------------------- */ /* We should now have a fully resolved path name. - * Lets see if we can get a lock on it. - */ + * Lets see if we can get a lock on it. */ - sess->lockfile = malloc (strlen (sess->fullpath) + 5); - strcpy (sess->lockfile, sess->fullpath); - strcat (sess->lockfile, ".LCK"); - - rc = stat (sess->lockfile, &statbuf); - if (!rc) { - /* oops .. file is all locked up .. */ - sess->errtype = ETXTBSY; - free (sess->sessionid); sess->sessionid = NULL; - free (sess->fullpath); sess->fullpath = NULL; - free (sess->lockfile); sess->lockfile = NULL; - return NULL; - } - sess->lockfd = open (sess->lockfile, O_RDWR | O_CREAT | O_EXCL , 0); - if (0 > sess->lockfd) { - /* oops .. file is all locked up .. */ - sess->errtype = ETXTBSY; - free (sess->sessionid); sess->sessionid = NULL; - free (sess->fullpath); sess->fullpath = NULL; - free (sess->lockfile); sess->lockfile = NULL; - return NULL; - } + sess->lockfile = g_strconcat(sess->fullpath, ".LCK", NULL); - /* OK, now work around some NFS atomic lock race condition - * mumbo-jumbo. We do this by linking a unique file, and - * then examing the link count. At least that's what the - * NFS programmers guide suggests. - * Note: the "unique filename" must be unique for the - * triplet filename-host-process, otherwise accidental - * aliases can occur. - */ - /* appearently, even this code may not work for some NFS - * implementations. In the long run, I am told that - * ftp.debian.org - * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz - * provides a better long-term solution. - */ - strcpy (pathbuf, sess->lockfile); - path = strrchr (pathbuf, '.'); - sprintf (path, ".%lx.%d.LNK", gethostid(), getpid()); - link (sess->lockfile, pathbuf); - rc = stat (sess->lockfile, &statbuf); - if (rc) { - /* oops .. stat failed! This can't happen! */ - sess->errtype = ETXTBSY; - unlink (pathbuf); - close (sess->lockfd); - unlink (sess->lockfile); - free (sess->sessionid); sess->sessionid = NULL; - free (sess->fullpath); sess->fullpath = NULL; - free (sess->lockfile); sess->lockfile = NULL; - return NULL; + if (!xaccSessionGetFileLock (sess)) { + if (!handler || !handler (sess->fullpath)) { + g_free (sess->sessionid); sess->sessionid = NULL; + g_free (sess->fullpath); sess->fullpath = NULL; + g_free (sess->lockfile); sess->lockfile = NULL; + return NULL; + } } - if (2 != statbuf.st_nlink) { - /* oops .. stat failed! This can't happen! */ - sess->errtype = ETXTBSY; - unlink (pathbuf); - close (sess->lockfd); - unlink (sess->lockfile); - free (sess->sessionid); sess->sessionid = NULL; - free (sess->fullpath); sess->fullpath = NULL; - free (sess->lockfile); sess->lockfile = NULL; - return NULL; - } - sess->linkfile = strdup (pathbuf); - /* ---------------------------------------------------- */ /* OK, if we've gotten this far, then we've succesfully obtained - * an atomic lock on the file. Go read the file contents ... - * well, read it only if it exists ... - */ + * an atomic lock on the file. Go read the file contents if it + * exists. */ sess->errtype = 0; sess->topgroup = NULL; @@ -387,13 +394,20 @@ xaccSessionEnd (Session *sess) if (sess->linkfile) unlink (sess->linkfile); if (0 < sess->lockfd) close (sess->lockfd); if (sess->lockfile) unlink (sess->lockfile); - if (sess->sessionid) free (sess->sessionid); sess->sessionid = NULL; - if (sess->fullpath) free (sess->fullpath); sess->fullpath = NULL; - if (sess->lockfile) free (sess->lockfile); sess->lockfile = NULL; - if (sess->linkfile) free (sess->linkfile); sess->linkfile = NULL; - sess->topgroup = NULL; - return; + g_free (sess->sessionid); + sess->sessionid = NULL; + + g_free (sess->fullpath); + sess->fullpath = NULL; + + g_free (sess->lockfile); + sess->lockfile = NULL; + + g_free (sess->linkfile); + sess->linkfile = NULL; + + sess->topgroup = NULL; } void @@ -401,7 +415,7 @@ xaccSessionDestroy (Session *sess) { if (!sess) return; xaccSessionEnd (sess); - free (sess); + g_free (sess); } @@ -415,29 +429,32 @@ MakeHomeDir (void) { int rc; struct stat statbuf; - char *home, *path; + char *home; + char *path; + char *data; /* Punt. Can't figure out where home is. */ home = getenv ("HOME"); if (!home) return; - path = alloca (strlen (home) +50); - strcpy (path, home); - strcat (path, "/.gnucash"); + path = g_strconcat(home, "/.gnucash", NULL); rc = stat (path, &statbuf); if (rc) { /* assume that the stat failed only because the dir is absent, * and not because its read-protected or other error. * Go ahead and make it. Don't bother much with checking mkdir - * for errors; seems pointless ... */ + * for errors; seems pointless. */ mkdir (path, S_IRWXU); /* perms = S_IRWXU = 0700 */ } - strcat (path, "/data"); - rc = stat (path, &statbuf); + data = g_strconcat (path, "/data"); + rc = stat (data, &statbuf); if (rc) - mkdir (path, S_IRWXU); + mkdir (data, S_IRWXU); + + g_free (path); + g_free (data); } /* ============================================================== */ diff --git a/src/engine/Session.h b/src/engine/Session.h index f5165d6afc..3cea2d358c 100644 --- a/src/engine/Session.h +++ b/src/engine/Session.h @@ -88,7 +88,12 @@ void xaccSessionDestroy (Session *); * * The xaccSessionBeginFile() routine is identical to the xaccSessionBegin() * routine, except that the argument is a filename (i.e. the five - * letters "file:" should not be prepended). + * letters "file:" should not be prepended) and there is an additional + * function argument. This function is called if xaccSessionBeginFile + * fails to obtain a lock for the file. If it returns true, the file + * is loaded anyway. If it returns false, or the handler is NULL, a + * failed lock attempt will abort the load. The lock fail handler is + * passed the filename of the data file being loaded. * * The xaccSessionGetFilePath() routine returns the fully-qualified file * path for the session. That is, if a relative or partial filename @@ -168,8 +173,11 @@ void xaccSessionDestroy (Session *); * xaccSessionDestroy (sess); * */ +typedef gboolean (*SessionLockFailHandler)(const char *file); + AccountGroup * xaccSessionBegin (Session *, const char * sessionid); -AccountGroup * xaccSessionBeginFile (Session *, const char * filename); +AccountGroup * xaccSessionBeginFile (Session *, const char * filename, + SessionLockFailHandler handler); int xaccSessionGetError (Session *); AccountGroup * xaccSessionGetGroup (Session *); void xaccSessionSetGroup (Session *, AccountGroup *topgroup);