521dad5888fde0e10237c9c6b741e071cd0c5cee
angie
  Tue May 14 09:47:56 2019 -0700
hgSession: added CGI param hgS_sessionDataDbSuffix to randomize choice of customData db* when bulk-converting old saved sessions to use sessionDataDir/sessionDataDbPrefix.  refs #22440

diff --git src/hg/hgSession/sessionData.c src/hg/hgSession/sessionData.c
index 826697a..46469e6 100644
--- src/hg/hgSession/sessionData.c
+++ src/hg/hgSession/sessionData.c
@@ -181,50 +181,42 @@
         else
             {
             // No new path -- trash file doesn't exist.  Remove from retString to avoid inf loop.
             char *newString = replaceChars(*retString, encTrashPath, "");
             freez(retString);
             *retString = newString;
             }
         freeMem(encTrashPath);
         freeMem(newPath);
         }
     if (urlEncoded)
         freeMem(trashDirPrefix);
     }
 }
 
-static unsigned dayOfMonth()
-/* Return the day of the month [1..31].  (Yeah, not [0..30]!  See man 3 localtime.) */
-{
-time_t now = time(NULL);
-struct tm *tm = localtime(&now);
-return tm->tm_mday;
-}
-
-static char *sessionDataDbTableName(char *tableName, char *sessionDataDbPrefix)
+static char *sessionDataDbTableName(char *tableName, char *sessionDataDbPrefix, char *dbSuffix)
 /* Alloc and return a new table name that includes a db derived from sessionDataDbPrefix. */
 {
-struct dyString *dy = dyStringCreate("%s%02u.%s", sessionDataDbPrefix, dayOfMonth(), tableName);
+struct dyString *dy = dyStringCreate("%s%s.%s", sessionDataDbPrefix, dbSuffix, tableName);
 return dyStringCannibalize(&dy);
 }
 
-static char *saveTrashTable(char *tableName, char *sessionDataDbPrefix)
+static char *saveTrashTable(char *tableName, char *sessionDataDbPrefix, char *dbSuffix)
 /* Move trash tableName out of customTrash to a sessionDataDbPrefix database, unless that
  * has been done already.  Return the new database.table name. */
 {
-char *newDbTableName = sessionDataDbTableName(tableName, sessionDataDbPrefix);
+char *newDbTableName = sessionDataDbTableName(tableName, sessionDataDbPrefix, dbSuffix);
 struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH);
 if (! sqlTableExists(conn, newDbTableName))
     {
     if (! sqlTableExists(conn, tableName))
         errAbort("saveTrashTable: neither "CUSTOM_TRASH".%s nor %s exist",
                  tableName, newDbTableName);
     struct dyString *dy = sqlDyStringCreate("rename table %s to %s", tableName, newDbTableName);
     sqlUpdate(conn, dy->string);
     dyStringFree(&dy);
     }
 else if (sqlTableExists(conn, tableName))
     errAbort("saveTrashTable: both %s and %s exist", tableName, newDbTableName);
 hFreeConn(&conn);
 return newDbTableName;
 }
@@ -270,121 +262,124 @@
                 {
                 char *newPath = saveTrashFile(actualTrashPath, sessionDir);
                 if (newPath)
                     replaceColumnValue(conn, tableName, columnName, newPath);
                 freeMem(newPath);
                 }
             dyStringFree(&dy);
             freeMem(trashPath);
             break;
             }
         }
     hFreeConn(&conn);
     }
 }
 
-static void saveDbTableName(char **retString, char *sessionDataDbPrefix, char *sessionDir)
+static void saveDbTableName(char **retString, char *sessionDataDbPrefix, char *dbSuffix,
+                            char *sessionDir)
 /* If sessionDataDbPrefix is given then scan for dbTableName setting; if found, move table and
  * update retString with new location.  Also, if table contains a trash path and sessionDir
  * is given, then replace the old trash path in the table with the new sessionDir location. */
 {
 char *prefix = "dbTableName";
 if (sessionDataDbPrefix)
     {
     int prefixLen = strlen(prefix);
     char *setting = stringIn(prefix, *retString);
     if (setting &&
         (setting[prefixLen] == '=' || setting[prefixLen] == ' '))
         {
         char *start = setting + prefixLen + 1;
         char quote = *start;
         if (quote == '\'' || quote == '"')
             start++;
         else
             quote = '\0';
         char *end = start;
         while (*end && ((quote && *end != quote) || (!quote && !isspace(*end))))
             end++;
         if (stringIn(prefix, end))
             errAbort("saveDbTableName: encountered two instances of '%s', expected 0 or 1",
                      prefix);
         char *tableName = cloneStringZ(start, (end - start));
         if (!startsWith(sessionDataDbPrefix, tableName))
             {
-            char *newDbTableName = saveTrashTable(tableName, sessionDataDbPrefix);
+            char *newDbTableName = saveTrashTable(tableName, sessionDataDbPrefix, dbSuffix);
             updateSessionDataTablePaths(newDbTableName, sessionDir);
             char *newString = replaceChars(*retString, tableName, newDbTableName);
             freez(retString);
             *retString = newString;
             freeMem(newDbTableName);
             }
         freeMem(tableName);
         }
     }
 }
 
 static char *newCtTrashFile()
 /* Alloc and return the name of a new trash file to hold custom track metadata. */
 {
 struct tempName tn;
 trashDirFile(&tn, "ct", CT_PREFIX, ".ctfile");
 return cloneString(tn.forCgi);
 }
 
 static char *saveTrackFile(struct cart *cart, char *varName, char *oldFile,
-                           char *sessionDataDbPrefix, char *sessionDir)
+                           char *sessionDataDbPrefix, char *dbSuffix, char *sessionDir)
 /* oldFile contains custom track lines or track collection hub trackDb; scan for trashDir paths
  * and/or customTrash tables and move files and tables to safe locations per sessionDataDbPrefix and
  * sessionDir.  If oldFile does not exist or has already been saved, return NULL. */
 {
 char *newFile = NULL;
 if (fileExists(oldFile))
     {
     if (isTrashPath(oldFile) && !maybeReadlink(oldFile))
         {
         struct lineFile *lf = lineFileOpen(oldFile, TRUE);
         if (isNotEmpty(sessionDir))
             newFile = sessionDataPathFromTrash(oldFile, sessionDir);
         else
             newFile = newCtTrashFile();
         if (fileExists(newFile))
             errAbort("saveTrackFile: new file '%s' already exists", newFile);
         makeDirsForFile(newFile);
         FILE *newF = mustOpen(newFile, "w");
         char *line;
         while (lineFileNext(lf, &line, NULL))
             {
             char *s = skipLeadingSpaces(line);
             if (*s != '\0' && *s != '#')
                 {
                 char *trackLine = cloneString(line);
                 saveTrashPaths(&trackLine, sessionDir, FALSE);
-                saveDbTableName(&trackLine, sessionDataDbPrefix, sessionDir);
+                saveDbTableName(&trackLine, sessionDataDbPrefix, dbSuffix, sessionDir);
                 fprintf(newF, "%s\n", trackLine);
                 freeMem(trackLine);
                 }
             else
                 fprintf(newF, "%s\n", line);
             }
         carefulClose(&newF);
+        fprintf(stderr, "Wrote new file %s\n", newFile);
         if (isNotEmpty(sessionDir))
             {
             if (unlink(oldFile) != 0)
                 errnoAbort("saveTrackFile: unlink(oldFile='%s') failed", oldFile);
             if (symlink(newFile, oldFile) != 0)
                 errnoAbort("saveTrackFile: symlink(newFile='%s', oldFile='%s') failed",
                            newFile, oldFile);
+            fprintf(stderr, "symlinked %s to %s\n", oldFile, newFile);
             }
         cartSetString(cart, varName, newFile);
         }
     }
 else
     cartRemove(cart, varName);
 return newFile;
 }
 
 char *sessionDirFromNames(char *sessionDataDir, char *encUserName, char *encSessionName)
 /* Alloc and return session data directory:
  * sessionDataDir/2ByteHashOfEncUserName/encUserName/8ByteHashOfEncSessionName
  * 2ByteHashOfEncUserName spreads userName values across up to 256 subdirectories because
  * we have ~15000 distinct namedSessionDb.userName values in 2019.
  * 8ByteHashOfEncSessionName because session names can be very long.  */
@@ -400,53 +395,66 @@
     sessionHash[8] = '\0';
     struct dyString *dy = dyStringCreate("%s/%s/%s/%s",
                                          sessionDataDir, userHash, encUserName, sessionHash);
     dir = dyStringCannibalize(&dy);
     freeMem(sessionHash);
     }
 return dir;
 }
 
 INLINE boolean cartVarIsCustomComposite(char *cartVar)
 /* Return TRUE if cartVar starts with "customComposite-". */
 {
 return startsWith(customCompositeCartName "-", cartVar);
 }
 
-void saveSessionData(struct cart *cart, char *encUserName, char *encSessionName)
+static char *dayOfMonthString()
+/* Return a two-character string with the current day of the month [01..31].  Do not free.
+ * (Yeah, not [0..30]!  See man 3 localtime.) */
+{
+static char dayString[16];
+time_t now = time(NULL);
+struct tm *tm = localtime(&now);
+safef(dayString, sizeof dayString, "%02u", tm->tm_mday);
+return dayString;
+}
+
+void saveSessionData(struct cart *cart, char *encUserName, char *encSessionName, char *dbSuffix)
 /* If hg.conf specifies safe places to store files and/or tables that belong to user sessions,
  * then scan cart for trashDir files and/or customTrash tables, store them in safe locations,
  * and update cart to point to the new locations. */
 {
 char *sessionDataDbPrefix = cfgOption("sessionDataDbPrefix");
 char *sessionDataDir = cfgOption("sessionDataDir");
 // Use (URL-encoded) userName and sessionName to make directory hierarchy under sessionDataDir
 char *sessionDir = sessionDirFromNames(sessionDataDir, encUserName, encSessionName);
 if (isNotEmpty(sessionDataDbPrefix) || isNotEmpty(sessionDir))
     {
+    if (isNotEmpty(sessionDataDbPrefix) && dbSuffix == NULL)
+        dbSuffix = dayOfMonthString();
     struct slPair *allVars = cartVarsLike(cart, "*");
     struct slPair *var;
     for (var = allVars;  var != NULL;  var = var->next)
         {
         if (startsWith(CT_FILE_VAR_PREFIX, var->name) ||
             cartVarIsCustomComposite(var->name))
             {
             // val is file that contains references to trash files and customTrash db tables;
             // replace with new file containing references to saved files and tables.
             char *oldTrackFile = cloneString(var->val);
             char *newTrackFile = saveTrackFile(cart, var->name, var->val,
-                                               sessionDataDbPrefix, sessionDir);
+                                               sessionDataDbPrefix, dbSuffix, sessionDir);
             if (newTrackFile && cartVarIsCustomComposite(var->name))
                 cartReplaceHubVars(cart, var->name, oldTrackFile, newTrackFile);
             freeMem(oldTrackFile);
             freeMem(newTrackFile);
             }
         else
             {
             // Regular cart var; save trash paths (possibly encoded) in value, if any are found.
             char *newVal = cloneString(var->val);
             saveTrashPaths(&newVal, sessionDir, FALSE);
             saveTrashPaths(&newVal, sessionDir, TRUE);
             if (newVal != var->val && differentString(newVal, var->val))
                 cartSetString(cart, var->name, newVal);
             freeMem(newVal);
             }