Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -675,30 +675,32 @@ ** C: in this example. */ if( !(i==2 && zName[1]==':') ){ #endif if( file_mkdir(zName, 1) ){ - fossil_panic("unable to create directory %s", zName); + fossil_fatal_recursive("unable to create directory %s", zName); + return 0; } #ifdef __MINGW32__ } #endif zName[i] = '/'; } } out = fopen(zName, "wb"); if( out==0 ){ - fossil_panic("unable to open file \"%s\" for writing", zName); + fossil_fatal_recursive("unable to open file \"%s\" for writing", zName); + return 0; } needToClose = 1; if( zName!=zBuf ) free(zName); } blob_is_init(pBlob); wrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out); if( needToClose ) fclose(out); if( wrote!=blob_size(pBlob) ){ - fossil_panic("short write: %d of %d bytes to %s", wrote, + fossil_fatal_recursive("short write: %d of %d bytes to %s", wrote, blob_size(pBlob), zFilename); } return wrote; } Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -99,11 +99,11 @@ } aHook[5]; static Stmt *pAllStmt = 0; /* List of all unfinalized statements */ /* ** This routine is called by the SQLite commit-hook mechanism -** just prior to each omit. All this routine does is verify +** just prior to each commit. All this routine does is verify ** that nBegin really is zero. That insures that transactions ** cannot commit by any means other than by calling db_end_transaction() ** below. ** ** This is just a safety and sanity check. @@ -138,18 +138,23 @@ db_multi_exec(doRollback ? "ROLLBACK" : "COMMIT"); doRollback = 0; } } void db_force_rollback(void){ + static int busy = 0; + if( busy ) return; + busy = 1; + undo_rollback(); if( nBegin ){ sqlite3_exec(g.db, "ROLLBACK", 0, 0, 0); if( isNewRepo ){ db_close(); unlink(g.zRepositoryName); } } nBegin = 0; + busy = 0; } /* ** Install a commit hook. Hooks are installed in sequence order. ** It is an error to install the same commit hook more than once. Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -257,16 +257,25 @@ aCommand[idx].xFunc(); return 0; } /* -** Print an error message, rollback all databases, and quit. +** The following variable becomes true while processing a fatal error +** or a panic. If additional "recursive-fatal" errors occur while +** shutting down, the recursive errors are silently ignored. +*/ +static int mainInFatalError = 0; + +/* +** Print an error message, rollback all databases, and quit. These +** routines never return. */ void fossil_panic(const char *zFormat, ...){ char *z; va_list ap; static int once = 1; + mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); if( g.cgiPanic && once ){ once = 0; @@ -279,10 +288,39 @@ exit(1); } void fossil_fatal(const char *zFormat, ...){ char *z; va_list ap; + mainInFatalError = 1; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + if( g.cgiPanic ){ + g.cgiPanic = 0; + cgi_printf("
%h
", z); + cgi_reply(); + }else{ + fprintf(stderr, "%s: %s\n", g.argv[0], z); + } + db_force_rollback(); + exit(1); +} + +/* This routine works like fossil_fatal() except that if called +** recursively, the recursive call is a no-op. +** +** Use this in places where an error might occur while doing +** fatal error shutdown processing. Unlike fossil_panic() and +** fossil_fatal() which never return, this routine might return if +** the fatal error handing is already in process. The caller must +** be prepared for this routine to return. +*/ +void fossil_fatal_recursive(const char *zFormat, ...){ + char *z; + va_list ap; + if( mainInFatalError ) return; + mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); if( g.cgiPanic ){ g.cgiPanic = 0; @@ -292,10 +330,13 @@ fprintf(stderr, "%s: %s\n", g.argv[0], z); } db_force_rollback(); exit(1); } + + +/* Print a warning message */ void fossil_warning(const char *zFormat, ...){ char *z; va_list ap; va_start(ap, zFormat); z = vmprintf(zFormat, ap); Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -286,7 +286,8 @@ /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); + undo_finish(); db_end_transaction(0); } Index: src/undo.c ================================================================== --- src/undo.c +++ src/undo.c @@ -85,23 +85,36 @@ } db_finalize(&q); } /* -** Undo or redo all undoable or redoable changes. +** Undo or redo changes to the filesystem. Undo the changes in the +** same order that they were originally carried out - undo the oldest +** change first and undo the most recent change last. */ -static void undo_all(int redoFlag){ +static void undo_all_filesystem(int redoFlag){ Stmt q; - int ucid; - int ncid; - db_prepare(&q, "SELECT pathname FROM undo WHERE redoflag=%d" - " ORDER BY +pathname", redoFlag); + db_prepare(&q, + "SELECT pathname FROM undo" + " WHERE redoflag=%d" + " ORDER BY rowid", + redoFlag + ); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q, 0); undo_one(zPathname, redoFlag); } db_finalize(&q); +} + +/* +** Undo or redo all undoable or redoable changes. +*/ +static void undo_all(int redoFlag){ + int ucid; + int ncid; + undo_all_filesystem(redoFlag); db_multi_exec( "CREATE TEMP TABLE undo_vfile_2 AS SELECT * FROM vfile;" "DELETE FROM vfile;" "INSERT INTO vfile SELECT * FROM undo_vfile;" "DELETE FROM undo_vfile;" @@ -126,16 +139,23 @@ void undo_reset(void){ static const char zSql[] = @ DROP TABLE IF EXISTS undo; @ DROP TABLE IF EXISTS undo_vfile; @ DROP TABLE IF EXISTS undo_vmerge; + @ DROP TABLE IF EXISTS undo_pending; ; db_multi_exec(zSql); db_lset_int("undo_available", 0); db_lset_int("undo_checkout", 0); } +/* +** This flag is true if we are in the process of collecting file changes +** for undo. When this flag is false, undo_save() is a no-op. +*/ +static int undoActive = 0; + /* ** Begin capturing a snapshot that can be undone. */ void undo_begin(void){ int cid; @@ -146,18 +166,29 @@ @ existsflag BOOLEAN, -- True if the file exists @ content BLOB -- Saved content @ ); @ CREATE TABLE undo_vfile AS SELECT * FROM vfile; @ CREATE TABLE undo_vmerge AS SELECT * FROM vmerge; + @ CREATE TABLE undo_pending(undoId INTEGER PRIMARY KEY); ; undo_reset(); db_multi_exec(zSql); cid = db_lget_int("checkout", 0); db_lset_int("undo_checkout", cid); db_lset_int("undo_available", 1); + undoActive = 1; } +/* +** This flag is true if one or more files have changed and have been +** recorded in the undo log but the undo log has not yet been committed. +** +** If a fatal error occurs and this flag is set, that means we should +** rollback all the filesystem changes. +*/ +static int undoNeedRollback = 0; + /* ** Save the current content of the file zPathname so that it ** will be undoable. The name is relative to the root of the ** tree. */ @@ -165,10 +196,11 @@ char *zFullname; Blob content; int existsFlag; Stmt q; + if( !undoActive ) return; zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); existsFlag = file_size(zFullname)>=0; db_prepare(&q, "REPLACE INTO undo(pathname,redoflag,existsflag,content)" " VALUES(%Q,0,%d,:c)", @@ -182,10 +214,39 @@ db_step(&q); db_finalize(&q); if( existsFlag ){ blob_reset(&content); } + undoNeedRollback = 1; +} + +/* +** Complete the undo process is one is currently in process. +*/ +void undo_finish(void){ + if( undoActive ){ + undoActive = 0; + undoNeedRollback = 0; + } +} + +/* +** This routine is called when the process aborts due to an error. +** If an undo was being accumulated but was not finished, attempt +** to rollback all of the filesystem changes. +** +** This rollback occurs, for example, if an "update" or "merge" operation +** could not run to completion because a file that needed to be written +** was locked or had permissions turned off. +*/ +void undo_rollback(void){ + if( !undoNeedRollback ) return; + assert( undoActive ); + undoNeedRollback = 0; + undoActive = 0; + printf("Rolling back prior filesystem changes...\n"); + undo_all_filesystem(0); } /* ** COMMAND: undo ** Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -119,11 +119,11 @@ " ORDER BY event.mtime DESC"); } db_begin_transaction(); vfile_check_signature(vid, 1); - undo_begin(); + if( !nochangeFlag ) undo_begin(); load_vfile_from_rid(tid); /* ** The record.fn field is used to match files against each other. The ** FV table contains one row for each each unique filename in @@ -288,10 +288,11 @@ }else{ /* A subset of files have been checked out. Keep the current ** checkout unchanged. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); } + undo_finish(); db_end_transaction(0); } } @@ -394,7 +395,8 @@ } blob_reset(&record); blob_reset(&fname); free(zFile); } + undo_finish(); db_end_transaction(0); }