11d5b26d9798079ec5adedc103216ce818e2da5e max Thu Sep 10 05:19:27 2026 -0700 Widen the session data directory hash from 8 to 10 hex characters sessionDirFromNames() named a session's durable data directory with 8 hex characters of md5(sessionName). 32 bits was fine while a directory only had to be unique among one user's sessions, but every anonymous "Share a link" session belongs to the single reserved user "l", which makes it a birthday problem across all of them: two unrelated sessions land in the same directory more likely than not at around 77,000 anonymous sessions, and at 500,000 we would expect about 29 such pairs. Two sessions sharing a directory means cleaning up one takes the other's custom track files with it. sessionDirHashLen is now 10 (40 bits), which moves the even-odds point past a million sessions. The two fan-out levels snapshotSessionDir() added for user "l" do not help here, since they are a prefix of the same hash: they spread the entries over 65536 directories but leave the number of distinct leaf names unchanged. Both directory layouts change name as a result, so snapshotCleaner would have walked past anything written earlier and orphaned its files. Both dir-naming functions grew a hashLen argument, sessionDirHashLenLegacy records the old value, and snapshotCleanAnon() now tries the old spelling of both layouts as well as the new one. Existing sessions keep working either way: the cart stores the absolute path of each durable file, so nothing looks a session's directory up by name except the cleaner. refs #10138 diff --git src/hg/lib/sessionData.c src/hg/lib/sessionData.c index dbf31ac2ea4..79b51a0b4ba 100644 --- src/hg/lib/sessionData.c +++ src/hg/lib/sessionData.c @@ -436,54 +436,67 @@ 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) +char *sessionDirFromNamesHashLen(char *sessionDataDir, char *encUserName, char *encSessionName, + int hashLen) /* Alloc and return session data directory: - * sessionDataDir/2ByteHashOfEncUserName/encUserName/8ByteHashOfEncSessionName + * sessionDataDir/2ByteHashOfEncUserName/encUserName/hashLenByteHashOfEncSessionName * 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. */ + * A hash of encSessionName rather than encSessionName itself because session names can be very + * long; hashLen is sessionDirHashLen except when naming a directory written before that was + * widened. */ { char *dir = NULL; if (isNotEmpty(sessionDataDir)) { if (sessionDataDir[0] != '/') errAbort("config setting sessionDataDir must be an absolute path (starting with '/')"); + if (hashLen < 1 || hashLen > 32) + errAbort("sessionDirFromNamesHashLen: hashLen must be in [1,32], got %d", hashLen); char *userHash = md5HexForString(encUserName); userHash[2] = '\0'; char *sessionHash = md5HexForString(encSessionName); - sessionHash[8] = '\0'; + sessionHash[hashLen] = '\0'; struct dyString *dy = dyStringCreate("%s/%s/%s/%s", sessionDataDir, userHash, encUserName, sessionHash); dir = dyStringCannibalize(&dy); + freeMem(userHash); freeMem(sessionHash); } return dir; } +char *sessionDirFromNames(char *sessionDataDir, char *encUserName, char *encSessionName) +/* Alloc and return the per-session data directory under sessionDataDir (hashed by user and session + * name), or NULL if sessionDataDir is empty. errAborts if sessionDataDir is not an absolute path. */ +{ +return sessionDirFromNamesHashLen(sessionDataDir, encUserName, encSessionName, sessionDirHashLen); +} + 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 sessionDataSaveSession(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,