de181afb7e1d76c969ab0ec23c0b71de5aa7f1e4
chmalee
  Fri Jul 18 11:25:13 2025 -0700
Fix hubspace pre-create hook to check for an apiKey only if a valid login cookie was not provided. Add a new hg.conf statement that allows for the data directory to be on a separate mount from the CGI, and swap strings around in the path appropriately to verify file existence when the file exists on another server

diff --git src/hg/lib/userdata.c src/hg/lib/userdata.c
index 0cca26e42c8..37416a52065 100644
--- src/hg/lib/userdata.c
+++ src/hg/lib/userdata.c
@@ -55,56 +55,76 @@
 // make this a global so if we have to repeatedly call stripDataDir()
 // we only need to check the filesystem for path validity once
 static char *dataDir = NULL;
 
 static char *setDataDir(char *userName)
 /* Set the dataDir value based on hg.conf and the userName. Use realpath to make sure
  * the directory exists and resolve the path if it is a symlink. Return the final
  * path for convenience */
 {
 char *tusdDataBaseDir = cfgOption("tusdDataDir");
 if (!tusdDataBaseDir  || isEmpty(tusdDataBaseDir))
     errAbort("trying to save user file but no tusdDataDir defined in hg.conf");
 if (tusdDataBaseDir[0] != '/')
     errAbort("config setting tusdDataDir must be an absolute path (starting with '/')");
 
+// the tusdDataBaseDir may be a symlink, so canonicalize it, but do not include
+// the userName part since it may not exist yet:
+char *canonicalPath = needMem(PATH_MAX);
+realpath(tusdDataBaseDir, canonicalPath);
+
 char *encUserName = cgiEncode(userName);
 char *userPrefix = md5HexForString(encUserName);
 userPrefix[2] = '\0';
 
-struct dyString *newDataDir = dyStringNew(0);
-dyStringPrintf(newDataDir, "%s/%s/%s",
-    tusdDataBaseDir, userPrefix, encUserName);
-
-char *canonicalPath = needMem(PATH_MAX);
-realpath(dyStringContents(newDataDir), canonicalPath);
-// now that we have canonicalized the path we need to add a '/' back on
+// now that we have a canonicalized the path we need to add a '/' back on
 // so the rest of the routines can append to this result
-
-dyStringClear(newDataDir);
-dyStringPrintf(newDataDir, "%s/", canonicalPath);
+struct dyString *newDataDir = dyStringNew(0);
+dyStringPrintf(newDataDir, "%s/%s/%s/",
+    canonicalPath, userPrefix, encUserName);
 
 dataDir = dyStringCannibalize(&newDataDir);
 return dataDir;
 }
 
 char *getDataDir(char *userName)
 /* Return the full path to the user specific data directory, can be configured via hg.conf
  * on hgwdev, this is /data/tusd */
 {
 if (!dataDir)
     setDataDir(userName);
+
+char *tusdMountPoint = cfgOption("tusdMountPoint");
+if (tusdMountPoint && !isEmpty(tusdMountPoint))
+    {
+    // the data server may be somewhere else and mounted over NFS. In this
+    // case, when tusd saves files, it is writing it's local tusdDataDir
+    // value into the hgcentral file location. When the CGI running somewhere
+    // else needs to verify file existence, the tusdDataDir won't exist on the
+    // CGI filesystem, but will instead be mounted as some different path.
+    // In this case, replace tusdDataDir with tusdMountPoint
+    char *tusdDataDir = cfgOption("tusdDataDir");
+    char *canonicalPath = needMem(PATH_MAX);
+    if (!startsWith(tusdDataDir, dataDir))
+        {
+        // could be a symlink
+        realpath(tusdDataDir, canonicalPath);
+        }
+    else
+        canonicalPath = tusdDataDir;
+    strSwapStrs(dataDir, strlen(dataDir), canonicalPath, tusdMountPoint);
+    }
 return dataDir;
 }
 
 char *stripDataDir(char *fname, char *userName)
 /* Strips the getDataDir(userName) off of fname. The dataDir may be a symbolic
  * link, we will resolve it here. NOTE that this relies on
  * calling realpath(3) on the fname argument prior to calling stripDataDir() */
 {
 getDataDir(userName);
 if (!dataDir)
     {
     // catch a realpath error
     return NULL;
     }
 int prefixSize = strlen(dataDir);