Fossil

Check-in [f87fb027]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:f87fb02780a03b1d128528ad1fda9bb78030443256fd329d7979d2738a845b66
User & Date: drh 2018-06-24 17:44:57
Context
2018-06-24
17:51
Improvements to the /test-warning webpage. check-in: 8d9ad750 user: drh tags: trunk
17:44
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. check-in: f87fb027 user: drh tags: trunk
16:38
Enhance the --sqltrace logic. Using those enhancements, locate and fix and unclosed transaction in the email alert sender logic. check-in: f3de8b66 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/db.c.

131
132
133
134
135
136
137








138
139
140
141
142
143
144
...
163
164
165
166
167
168
169
170



171
172
173
174
175
176
177
....
1763
1764
1765
1766
1767
1768
1769


1770

1771
1772
1773
1774
1775
1776
1777
1778
1779
/*
** Arrange for the given file to be deleted on a failure.
*/
void db_delete_on_failure(const char *zFilename){
  assert( db.nDeleteOnFail<count(db.azDeleteOnFail) );
  db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename);
}









/*
** This routine is called by the SQLite commit-hook mechanism
** 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.
................................................................................
    db.nPriorChanges = sqlite3_total_changes(g.db);
    db.doRollback = 0;
  }
  db.nBegin++;
}
void db_end_transaction(int rollbackFlag){
  if( g.db==0 ) return;
  if( db.nBegin<=0 ) return;



  if( rollbackFlag ){
    db.doRollback = 1;
    if( g.fSqlTrace ) fossil_trace("-- ROLLBACK by request\n");
  }
  db.nBegin--;
  if( db.nBegin==0 ){
    int i;
................................................................................
    sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &cur, &hiwtr, 0);
    fprintf(stderr, "-- PCACHE_OVFLOW          %10d %10d\n", cur, hiwtr);
    fprintf(stderr, "-- prepared statements    %10d\n", db.nPrepare);
  }
  while( db.pAllStmt ){
    db_finalize(db.pAllStmt);
  }


  db_end_transaction(1);

  pStmt = 0;
  g.dbIgnoreErrors++;  /* Stop "database locked" warnings from PRAGMA optimize */
  sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
  g.dbIgnoreErrors--;
  db_close_config();

  /* If the localdb has a lot of unused free space,
  ** then VACUUM it as we shut down.
  */







>
>
>
>
>
>
>
>







 







|
>
>
>







 







>
>
|
>

|







131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
...
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
....
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
/*
** Arrange for the given file to be deleted on a failure.
*/
void db_delete_on_failure(const char *zFilename){
  assert( db.nDeleteOnFail<count(db.azDeleteOnFail) );
  db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename);
}

/*
** Return the transaction nesting depth.  0 means we are currently
** not in a transaction.
*/
int db_transaction_nesting_depth(void){
  return db.nBegin;
}

/*
** This routine is called by the SQLite commit-hook mechanism
** 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.
................................................................................
    db.nPriorChanges = sqlite3_total_changes(g.db);
    db.doRollback = 0;
  }
  db.nBegin++;
}
void db_end_transaction(int rollbackFlag){
  if( g.db==0 ) return;
  if( db.nBegin<=0 ){
    fossil_warning("Extra call to db_end_transaction\n");
    return;
  }
  if( rollbackFlag ){
    db.doRollback = 1;
    if( g.fSqlTrace ) fossil_trace("-- ROLLBACK by request\n");
  }
  db.nBegin--;
  if( db.nBegin==0 ){
    int i;
................................................................................
    sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &cur, &hiwtr, 0);
    fprintf(stderr, "-- PCACHE_OVFLOW          %10d %10d\n", cur, hiwtr);
    fprintf(stderr, "-- prepared statements    %10d\n", db.nPrepare);
  }
  while( db.pAllStmt ){
    db_finalize(db.pAllStmt);
  }
  if( db.nBegin ){
    fossil_warning("Missed call to db_end_transaction(). Rolling back.\n");
    db_end_transaction(1);
  }
  pStmt = 0;
  g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */
  sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
  g.dbIgnoreErrors--;
  db_close_config();

  /* If the localdb has a lot of unused free space,
  ** then VACUUM it as we shut down.
  */

Changes to src/email.c.

1770
1771
1772
1773
1774
1775
1776




1777
1778
1779
1780
1781
1782
1783
1784

1785
1786
1787
1788
1789
1790

1791
1792
1793
1794
1795
1796
1797
**
** This routine is called after certain webpages have been run and
** have already responded.
*/
void email_auto_exec(void){
  int iJulianDay;
  if( g.db==0 ) return;




  db_begin_transaction();
  if( !email_tables_exist() ) goto autoexec_done;
  if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done;
  if( !db_exists("SELECT 1 FROM pending_alert") ) goto autoexec_done;
  email_send_alerts(0);
  iJulianDay = db_int(0, "SELECT julianday('now')");
  if( g.fSqlTrace ) fossil_trace("-- iJulianDay: %d\n", iJulianDay);
  if( iJulianDay>db_get_int("email-last-digest",0) ){

    db_multi_exec(
      "REPLACE INTO repository.config(name,value,mtime)"
      " VALUES('email-last-digest',%d,now())",
      iJulianDay
    );
    email_send_alerts(SENDALERT_DIGEST);

  }

autoexec_done:
  db_end_transaction(0);
}

/*







>
>
>
>






<

>
|
|
|
<
<
|
>







1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786

1787
1788
1789
1790
1791


1792
1793
1794
1795
1796
1797
1798
1799
1800
**
** This routine is called after certain webpages have been run and
** have already responded.
*/
void email_auto_exec(void){
  int iJulianDay;
  if( g.db==0 ) return;
  if( db_transaction_nesting_depth()!=0 ){
    fossil_warning("Called email_auto_exec() from within a transaction");
    return;
  }
  db_begin_transaction();
  if( !email_tables_exist() ) goto autoexec_done;
  if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done;
  if( !db_exists("SELECT 1 FROM pending_alert") ) goto autoexec_done;
  email_send_alerts(0);
  iJulianDay = db_int(0, "SELECT julianday('now')");

  if( iJulianDay>db_get_int("email-last-digest",0) ){
    if( db_transaction_nesting_depth()!=1 ){
      fossil_warning("Transaction nesting error prior to digest processing");
    }else{
      db_set_int("email-last-digest",iJulianDay,0);


      email_send_alerts(SENDALERT_DIGEST);
    }
  }

autoexec_done:
  db_end_transaction(0);
}

/*

Changes to src/main.c.

2506
2507
2508
2509
2510
2511
2512




2513
2514
2515
2516
2517
2518
2519
....
2705
2706
2707
2708
2709
2710
2711


































#endif

#if defined(_WIN32)
  const char *zStopperFile;    /* Name of file used to terminate server */
  zStopperFile = find_option("stopper", 0, 1);
#endif





  zFileGlob = find_option("files-urlenc",0,1);
  if( zFileGlob ){
    char *z = mprintf("%s", zFileGlob);
    dehttpize(z);
    zFileGlob = z;
  }else{
    zFileGlob = find_option("files",0,1);
................................................................................
      for(j=0; (c = z[j])!=0; j++){
        fossil_print("%02x", c);
      }
      fossil_print("]\n");
    }
  }
}









































>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
....
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
#endif

#if defined(_WIN32)
  const char *zStopperFile;    /* Name of file used to terminate server */
  zStopperFile = find_option("stopper", 0, 1);
#endif

  if( g.zErrlog==0 ){
    g.zErrlog = "-";
    g.fAnyTrace = 1;
  }
  zFileGlob = find_option("files-urlenc",0,1);
  if( zFileGlob ){
    char *z = mprintf("%s", zFileGlob);
    dehttpize(z);
    zFileGlob = z;
  }else{
    zFileGlob = find_option("files",0,1);
................................................................................
      for(j=0; (c = z[j])!=0; j++){
        fossil_print("%02x", c);
      }
      fossil_print("]\n");
    }
  }
}

/*
** WEBPAGE: test-warning
**
** Test error and warning log operation.  This webpage is accessible to
** the administrator only.
**
**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
*/
void test_warning_page(void){
  int iCase = atoi(PD("case","1"));
  login_check_credentials();
  if( !g.perm.Admin ){ fossil_redirect_home(); return; }
  style_header("Warning Test Page");
  @ <p>This is the test page for case=%d(iCase)</p>
  @ <ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");
  }
  @ <li value='2'> Call db_begin_transaction()
  if( iCase==2 ){
    db_begin_transaction();
  }
  @ <li value='3'> Call db_end_transaction()
  if( iCase==3 ){
    db_end_transaction(0);
  }
  @ </ol>
  @ <p>End of test</p>
  style_footer();
}

Changes to src/printf.c.

990
991
992
993
994
995
996



997
998

999
1000
1001
1002
1003
1004
1005
  const char *z;
  int i;
  va_list ap;
  static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT",
      "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
      "REQUEST_URI", "SCRIPT_NAME" };
  if( g.zErrlog==0 ) return;



  out = fossil_fopen(g.zErrlog, "a");
  if( out==0 ) return;

  now = time(0);
  pNow = gmtime(&now);
  fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n",
          pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1,
          pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
  va_start(ap, zFormat);
  vfprintf(out, zFormat, ap);







>
>
>
|
|
>







990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
  const char *z;
  int i;
  va_list ap;
  static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT",
      "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
      "REQUEST_URI", "SCRIPT_NAME" };
  if( g.zErrlog==0 ) return;
  if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){
    out = stderr;
  }else{
    out = fossil_fopen(g.zErrlog, "a");
    if( out==0 ) return;
  }
  now = time(0);
  pNow = gmtime(&now);
  fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n",
          pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1,
          pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
  va_start(ap, zFormat);
  vfprintf(out, zFormat, ap);