Fossil SCM
Extra defenses against running the digest alert generator in a context where the transaction will rollback, thus failing to record the new digest time. Change the "fossil server" and "fossil ui" commands to always log errors to the console if no other error logging is defined.
Commit
f87fb02780a03b1d128528ad1fda9bb78030443256fd329d7979d2738a845b66
Parent
f3de8b663256229…
4 files changed
+17
-3
+10
-7
+38
+6
-2
M
src/db.c
+17
-3
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -133,10 +133,18 @@ | ||
| 133 | 133 | */ |
| 134 | 134 | void db_delete_on_failure(const char *zFilename){ |
| 135 | 135 | assert( db.nDeleteOnFail<count(db.azDeleteOnFail) ); |
| 136 | 136 | db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename); |
| 137 | 137 | } |
| 138 | + | |
| 139 | +/* | |
| 140 | +** Return the transaction nesting depth. 0 means we are currently | |
| 141 | +** not in a transaction. | |
| 142 | +*/ | |
| 143 | +int db_transaction_nesting_depth(void){ | |
| 144 | + return db.nBegin; | |
| 145 | +} | |
| 138 | 146 | |
| 139 | 147 | /* |
| 140 | 148 | ** This routine is called by the SQLite commit-hook mechanism |
| 141 | 149 | ** just prior to each commit. All this routine does is verify |
| 142 | 150 | ** that nBegin really is zero. That insures that transactions |
| @@ -165,11 +173,14 @@ | ||
| 165 | 173 | } |
| 166 | 174 | db.nBegin++; |
| 167 | 175 | } |
| 168 | 176 | void db_end_transaction(int rollbackFlag){ |
| 169 | 177 | if( g.db==0 ) return; |
| 170 | - if( db.nBegin<=0 ) return; | |
| 178 | + if( db.nBegin<=0 ){ | |
| 179 | + fossil_warning("Extra call to db_end_transaction\n"); | |
| 180 | + return; | |
| 181 | + } | |
| 171 | 182 | if( rollbackFlag ){ |
| 172 | 183 | db.doRollback = 1; |
| 173 | 184 | if( g.fSqlTrace ) fossil_trace("-- ROLLBACK by request\n"); |
| 174 | 185 | } |
| 175 | 186 | db.nBegin--; |
| @@ -1765,13 +1776,16 @@ | ||
| 1765 | 1776 | fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare); |
| 1766 | 1777 | } |
| 1767 | 1778 | while( db.pAllStmt ){ |
| 1768 | 1779 | db_finalize(db.pAllStmt); |
| 1769 | 1780 | } |
| 1770 | - db_end_transaction(1); | |
| 1781 | + if( db.nBegin ){ | |
| 1782 | + fossil_warning("Missed call to db_end_transaction(). Rolling back.\n"); | |
| 1783 | + db_end_transaction(1); | |
| 1784 | + } | |
| 1771 | 1785 | pStmt = 0; |
| 1772 | - g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */ | |
| 1786 | + g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */ | |
| 1773 | 1787 | sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0); |
| 1774 | 1788 | g.dbIgnoreErrors--; |
| 1775 | 1789 | db_close_config(); |
| 1776 | 1790 | |
| 1777 | 1791 | /* If the localdb has a lot of unused free space, |
| 1778 | 1792 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -133,10 +133,18 @@ | |
| 133 | */ |
| 134 | void db_delete_on_failure(const char *zFilename){ |
| 135 | assert( db.nDeleteOnFail<count(db.azDeleteOnFail) ); |
| 136 | db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename); |
| 137 | } |
| 138 | |
| 139 | /* |
| 140 | ** This routine is called by the SQLite commit-hook mechanism |
| 141 | ** just prior to each commit. All this routine does is verify |
| 142 | ** that nBegin really is zero. That insures that transactions |
| @@ -165,11 +173,14 @@ | |
| 165 | } |
| 166 | db.nBegin++; |
| 167 | } |
| 168 | void db_end_transaction(int rollbackFlag){ |
| 169 | if( g.db==0 ) return; |
| 170 | if( db.nBegin<=0 ) return; |
| 171 | if( rollbackFlag ){ |
| 172 | db.doRollback = 1; |
| 173 | if( g.fSqlTrace ) fossil_trace("-- ROLLBACK by request\n"); |
| 174 | } |
| 175 | db.nBegin--; |
| @@ -1765,13 +1776,16 @@ | |
| 1765 | fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare); |
| 1766 | } |
| 1767 | while( db.pAllStmt ){ |
| 1768 | db_finalize(db.pAllStmt); |
| 1769 | } |
| 1770 | db_end_transaction(1); |
| 1771 | pStmt = 0; |
| 1772 | g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */ |
| 1773 | sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0); |
| 1774 | g.dbIgnoreErrors--; |
| 1775 | db_close_config(); |
| 1776 | |
| 1777 | /* If the localdb has a lot of unused free space, |
| 1778 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -133,10 +133,18 @@ | |
| 133 | */ |
| 134 | void db_delete_on_failure(const char *zFilename){ |
| 135 | assert( db.nDeleteOnFail<count(db.azDeleteOnFail) ); |
| 136 | db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename); |
| 137 | } |
| 138 | |
| 139 | /* |
| 140 | ** Return the transaction nesting depth. 0 means we are currently |
| 141 | ** not in a transaction. |
| 142 | */ |
| 143 | int db_transaction_nesting_depth(void){ |
| 144 | return db.nBegin; |
| 145 | } |
| 146 | |
| 147 | /* |
| 148 | ** This routine is called by the SQLite commit-hook mechanism |
| 149 | ** just prior to each commit. All this routine does is verify |
| 150 | ** that nBegin really is zero. That insures that transactions |
| @@ -165,11 +173,14 @@ | |
| 173 | } |
| 174 | db.nBegin++; |
| 175 | } |
| 176 | void db_end_transaction(int rollbackFlag){ |
| 177 | if( g.db==0 ) return; |
| 178 | if( db.nBegin<=0 ){ |
| 179 | fossil_warning("Extra call to db_end_transaction\n"); |
| 180 | return; |
| 181 | } |
| 182 | if( rollbackFlag ){ |
| 183 | db.doRollback = 1; |
| 184 | if( g.fSqlTrace ) fossil_trace("-- ROLLBACK by request\n"); |
| 185 | } |
| 186 | db.nBegin--; |
| @@ -1765,13 +1776,16 @@ | |
| 1776 | fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare); |
| 1777 | } |
| 1778 | while( db.pAllStmt ){ |
| 1779 | db_finalize(db.pAllStmt); |
| 1780 | } |
| 1781 | if( db.nBegin ){ |
| 1782 | fossil_warning("Missed call to db_end_transaction(). Rolling back.\n"); |
| 1783 | db_end_transaction(1); |
| 1784 | } |
| 1785 | pStmt = 0; |
| 1786 | g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */ |
| 1787 | sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0); |
| 1788 | g.dbIgnoreErrors--; |
| 1789 | db_close_config(); |
| 1790 | |
| 1791 | /* If the localdb has a lot of unused free space, |
| 1792 |
+10
-7
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -1772,24 +1772,27 @@ | ||
| 1772 | 1772 | ** have already responded. |
| 1773 | 1773 | */ |
| 1774 | 1774 | void email_auto_exec(void){ |
| 1775 | 1775 | int iJulianDay; |
| 1776 | 1776 | if( g.db==0 ) return; |
| 1777 | + if( db_transaction_nesting_depth()!=0 ){ | |
| 1778 | + fossil_warning("Called email_auto_exec() from within a transaction"); | |
| 1779 | + return; | |
| 1780 | + } | |
| 1777 | 1781 | db_begin_transaction(); |
| 1778 | 1782 | if( !email_tables_exist() ) goto autoexec_done; |
| 1779 | 1783 | if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done; |
| 1780 | 1784 | if( !db_exists("SELECT 1 FROM pending_alert") ) goto autoexec_done; |
| 1781 | 1785 | email_send_alerts(0); |
| 1782 | 1786 | iJulianDay = db_int(0, "SELECT julianday('now')"); |
| 1783 | - if( g.fSqlTrace ) fossil_trace("-- iJulianDay: %d\n", iJulianDay); | |
| 1784 | 1787 | if( iJulianDay>db_get_int("email-last-digest",0) ){ |
| 1785 | - db_multi_exec( | |
| 1786 | - "REPLACE INTO repository.config(name,value,mtime)" | |
| 1787 | - " VALUES('email-last-digest',%d,now())", | |
| 1788 | - iJulianDay | |
| 1789 | - ); | |
| 1790 | - email_send_alerts(SENDALERT_DIGEST); | |
| 1788 | + if( db_transaction_nesting_depth()!=1 ){ | |
| 1789 | + fossil_warning("Transaction nesting error prior to digest processing"); | |
| 1790 | + }else{ | |
| 1791 | + db_set_int("email-last-digest",iJulianDay,0); | |
| 1792 | + email_send_alerts(SENDALERT_DIGEST); | |
| 1793 | + } | |
| 1791 | 1794 | } |
| 1792 | 1795 | |
| 1793 | 1796 | autoexec_done: |
| 1794 | 1797 | db_end_transaction(0); |
| 1795 | 1798 | } |
| 1796 | 1799 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -1772,24 +1772,27 @@ | |
| 1772 | ** have already responded. |
| 1773 | */ |
| 1774 | void email_auto_exec(void){ |
| 1775 | int iJulianDay; |
| 1776 | if( g.db==0 ) return; |
| 1777 | db_begin_transaction(); |
| 1778 | if( !email_tables_exist() ) goto autoexec_done; |
| 1779 | if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done; |
| 1780 | if( !db_exists("SELECT 1 FROM pending_alert") ) goto autoexec_done; |
| 1781 | email_send_alerts(0); |
| 1782 | iJulianDay = db_int(0, "SELECT julianday('now')"); |
| 1783 | if( g.fSqlTrace ) fossil_trace("-- iJulianDay: %d\n", iJulianDay); |
| 1784 | if( iJulianDay>db_get_int("email-last-digest",0) ){ |
| 1785 | db_multi_exec( |
| 1786 | "REPLACE INTO repository.config(name,value,mtime)" |
| 1787 | " VALUES('email-last-digest',%d,now())", |
| 1788 | iJulianDay |
| 1789 | ); |
| 1790 | email_send_alerts(SENDALERT_DIGEST); |
| 1791 | } |
| 1792 | |
| 1793 | autoexec_done: |
| 1794 | db_end_transaction(0); |
| 1795 | } |
| 1796 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -1772,24 +1772,27 @@ | |
| 1772 | ** have already responded. |
| 1773 | */ |
| 1774 | void email_auto_exec(void){ |
| 1775 | int iJulianDay; |
| 1776 | if( g.db==0 ) return; |
| 1777 | if( db_transaction_nesting_depth()!=0 ){ |
| 1778 | fossil_warning("Called email_auto_exec() from within a transaction"); |
| 1779 | return; |
| 1780 | } |
| 1781 | db_begin_transaction(); |
| 1782 | if( !email_tables_exist() ) goto autoexec_done; |
| 1783 | if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done; |
| 1784 | if( !db_exists("SELECT 1 FROM pending_alert") ) goto autoexec_done; |
| 1785 | email_send_alerts(0); |
| 1786 | iJulianDay = db_int(0, "SELECT julianday('now')"); |
| 1787 | if( iJulianDay>db_get_int("email-last-digest",0) ){ |
| 1788 | if( db_transaction_nesting_depth()!=1 ){ |
| 1789 | fossil_warning("Transaction nesting error prior to digest processing"); |
| 1790 | }else{ |
| 1791 | db_set_int("email-last-digest",iJulianDay,0); |
| 1792 | email_send_alerts(SENDALERT_DIGEST); |
| 1793 | } |
| 1794 | } |
| 1795 | |
| 1796 | autoexec_done: |
| 1797 | db_end_transaction(0); |
| 1798 | } |
| 1799 |
+38
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -2508,10 +2508,14 @@ | ||
| 2508 | 2508 | #if defined(_WIN32) |
| 2509 | 2509 | const char *zStopperFile; /* Name of file used to terminate server */ |
| 2510 | 2510 | zStopperFile = find_option("stopper", 0, 1); |
| 2511 | 2511 | #endif |
| 2512 | 2512 | |
| 2513 | + if( g.zErrlog==0 ){ | |
| 2514 | + g.zErrlog = "-"; | |
| 2515 | + g.fAnyTrace = 1; | |
| 2516 | + } | |
| 2513 | 2517 | zFileGlob = find_option("files-urlenc",0,1); |
| 2514 | 2518 | if( zFileGlob ){ |
| 2515 | 2519 | char *z = mprintf("%s", zFileGlob); |
| 2516 | 2520 | dehttpize(z); |
| 2517 | 2521 | zFileGlob = z; |
| @@ -2707,5 +2711,39 @@ | ||
| 2707 | 2711 | } |
| 2708 | 2712 | fossil_print("]\n"); |
| 2709 | 2713 | } |
| 2710 | 2714 | } |
| 2711 | 2715 | } |
| 2716 | + | |
| 2717 | +/* | |
| 2718 | +** WEBPAGE: test-warning | |
| 2719 | +** | |
| 2720 | +** Test error and warning log operation. This webpage is accessible to | |
| 2721 | +** the administrator only. | |
| 2722 | +** | |
| 2723 | +** case=1 Issue a fossil_warning() while generating the page. | |
| 2724 | +** case=2 Extra db_begin_transaction() | |
| 2725 | +** case=3 Extra db_end_transaction() | |
| 2726 | +*/ | |
| 2727 | +void test_warning_page(void){ | |
| 2728 | + int iCase = atoi(PD("case","1")); | |
| 2729 | + login_check_credentials(); | |
| 2730 | + if( !g.perm.Admin ){ fossil_redirect_home(); return; } | |
| 2731 | + style_header("Warning Test Page"); | |
| 2732 | + @ <p>This is the test page for case=%d(iCase)</p> | |
| 2733 | + @ <ol> | |
| 2734 | + @ <li value='1'> Call fossil_warning() | |
| 2735 | + if( iCase==1 ){ | |
| 2736 | + fossil_warning("Test warning message from /test-warning"); | |
| 2737 | + } | |
| 2738 | + @ <li value='2'> Call db_begin_transaction() | |
| 2739 | + if( iCase==2 ){ | |
| 2740 | + db_begin_transaction(); | |
| 2741 | + } | |
| 2742 | + @ <li value='3'> Call db_end_transaction() | |
| 2743 | + if( iCase==3 ){ | |
| 2744 | + db_end_transaction(0); | |
| 2745 | + } | |
| 2746 | + @ </ol> | |
| 2747 | + @ <p>End of test</p> | |
| 2748 | + style_footer(); | |
| 2749 | +} | |
| 2712 | 2750 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2508,10 +2508,14 @@ | |
| 2508 | #if defined(_WIN32) |
| 2509 | const char *zStopperFile; /* Name of file used to terminate server */ |
| 2510 | zStopperFile = find_option("stopper", 0, 1); |
| 2511 | #endif |
| 2512 | |
| 2513 | zFileGlob = find_option("files-urlenc",0,1); |
| 2514 | if( zFileGlob ){ |
| 2515 | char *z = mprintf("%s", zFileGlob); |
| 2516 | dehttpize(z); |
| 2517 | zFileGlob = z; |
| @@ -2707,5 +2711,39 @@ | |
| 2707 | } |
| 2708 | fossil_print("]\n"); |
| 2709 | } |
| 2710 | } |
| 2711 | } |
| 2712 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2508,10 +2508,14 @@ | |
| 2508 | #if defined(_WIN32) |
| 2509 | const char *zStopperFile; /* Name of file used to terminate server */ |
| 2510 | zStopperFile = find_option("stopper", 0, 1); |
| 2511 | #endif |
| 2512 | |
| 2513 | if( g.zErrlog==0 ){ |
| 2514 | g.zErrlog = "-"; |
| 2515 | g.fAnyTrace = 1; |
| 2516 | } |
| 2517 | zFileGlob = find_option("files-urlenc",0,1); |
| 2518 | if( zFileGlob ){ |
| 2519 | char *z = mprintf("%s", zFileGlob); |
| 2520 | dehttpize(z); |
| 2521 | zFileGlob = z; |
| @@ -2707,5 +2711,39 @@ | |
| 2711 | } |
| 2712 | fossil_print("]\n"); |
| 2713 | } |
| 2714 | } |
| 2715 | } |
| 2716 | |
| 2717 | /* |
| 2718 | ** WEBPAGE: test-warning |
| 2719 | ** |
| 2720 | ** Test error and warning log operation. This webpage is accessible to |
| 2721 | ** the administrator only. |
| 2722 | ** |
| 2723 | ** case=1 Issue a fossil_warning() while generating the page. |
| 2724 | ** case=2 Extra db_begin_transaction() |
| 2725 | ** case=3 Extra db_end_transaction() |
| 2726 | */ |
| 2727 | void test_warning_page(void){ |
| 2728 | int iCase = atoi(PD("case","1")); |
| 2729 | login_check_credentials(); |
| 2730 | if( !g.perm.Admin ){ fossil_redirect_home(); return; } |
| 2731 | style_header("Warning Test Page"); |
| 2732 | @ <p>This is the test page for case=%d(iCase)</p> |
| 2733 | @ <ol> |
| 2734 | @ <li value='1'> Call fossil_warning() |
| 2735 | if( iCase==1 ){ |
| 2736 | fossil_warning("Test warning message from /test-warning"); |
| 2737 | } |
| 2738 | @ <li value='2'> Call db_begin_transaction() |
| 2739 | if( iCase==2 ){ |
| 2740 | db_begin_transaction(); |
| 2741 | } |
| 2742 | @ <li value='3'> Call db_end_transaction() |
| 2743 | if( iCase==3 ){ |
| 2744 | db_end_transaction(0); |
| 2745 | } |
| 2746 | @ </ol> |
| 2747 | @ <p>End of test</p> |
| 2748 | style_footer(); |
| 2749 | } |
| 2750 |
+6
-2
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -992,12 +992,16 @@ | ||
| 992 | 992 | va_list ap; |
| 993 | 993 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT", |
| 994 | 994 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 995 | 995 | "REQUEST_URI", "SCRIPT_NAME" }; |
| 996 | 996 | if( g.zErrlog==0 ) return; |
| 997 | - out = fossil_fopen(g.zErrlog, "a"); | |
| 998 | - if( out==0 ) return; | |
| 997 | + if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){ | |
| 998 | + out = stderr; | |
| 999 | + }else{ | |
| 1000 | + out = fossil_fopen(g.zErrlog, "a"); | |
| 1001 | + if( out==0 ) return; | |
| 1002 | + } | |
| 999 | 1003 | now = time(0); |
| 1000 | 1004 | pNow = gmtime(&now); |
| 1001 | 1005 | fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n", |
| 1002 | 1006 | pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1, |
| 1003 | 1007 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1004 | 1008 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -992,12 +992,16 @@ | |
| 992 | va_list ap; |
| 993 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT", |
| 994 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 995 | "REQUEST_URI", "SCRIPT_NAME" }; |
| 996 | if( g.zErrlog==0 ) return; |
| 997 | out = fossil_fopen(g.zErrlog, "a"); |
| 998 | if( out==0 ) return; |
| 999 | now = time(0); |
| 1000 | pNow = gmtime(&now); |
| 1001 | fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n", |
| 1002 | pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1, |
| 1003 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1004 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -992,12 +992,16 @@ | |
| 992 | va_list ap; |
| 993 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT", |
| 994 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 995 | "REQUEST_URI", "SCRIPT_NAME" }; |
| 996 | if( g.zErrlog==0 ) return; |
| 997 | if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){ |
| 998 | out = stderr; |
| 999 | }else{ |
| 1000 | out = fossil_fopen(g.zErrlog, "a"); |
| 1001 | if( out==0 ) return; |
| 1002 | } |
| 1003 | now = time(0); |
| 1004 | pNow = gmtime(&now); |
| 1005 | fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n", |
| 1006 | pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1, |
| 1007 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1008 |