685a30331929add5218e4cae8dcff9fef35e9a0a
chmalee
  Thu Jul 31 15:46:11 2025 -0700
Feedback from code review and hgw0 testing of hubspace. If the hubspace filesystem is mounted, do the replacement in the dataDir string when first setting the dataDir (code review). Later, when stripping the dataDir off the filename to print in the UI, add a check for the mount point so we can correctly strip off the right string from the incoming filename, which will have a different path prefix than on the hbuspace machine.

diff --git src/hg/lib/userdata.c src/hg/lib/userdata.c
index 37416a52065..8bac253f375 100644
--- src/hg/lib/userdata.c
+++ src/hg/lib/userdata.c
@@ -56,95 +56,108 @@
 // 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:
+// the userName part since it may not exist yet.
 char *canonicalPath = needMem(PATH_MAX);
-realpath(tusdDataBaseDir, canonicalPath);
+char *retValue = realpath(tusdDataBaseDir, canonicalPath);
+if (!retValue)
+    {
+    // realpath returned NULL, check if we need to swap with a mounted filesystem
+    char *swapped = swapDataDir(userName, tusdDataBaseDir);
+    retValue = realpath(swapped, canonicalPath);
+    if (!retValue)
+        errAbort("cannot resolve tusdDataDir nor tusdMountPoint");
+    }
 
 char *encUserName = cgiEncode(userName);
 char *userPrefix = md5HexForString(encUserName);
 userPrefix[2] = '\0';
 
 // 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
 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))
+return dataDir;
+}
+
+char *swapDataDir(char *userName, char *in)
+/* Try replacing the current dataDir with what is defined in hg.conf:tusdMountPoint as
+ * 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 */
 {
-    // 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 *ret = cloneString(in);
 char *tusdDataDir = cfgOption("tusdDataDir");
-    char *canonicalPath = needMem(PATH_MAX);
-    if (!startsWith(tusdDataDir, dataDir))
+char *tusdMountPoint = cfgOption("tusdMountPoint");
+if (tusdMountPoint && !isEmpty(tusdMountPoint))
     {
-        // could be a symlink
-        realpath(tusdDataDir, canonicalPath);
-        }
-    else
-        canonicalPath = tusdDataDir;
-    strSwapStrs(dataDir, strlen(dataDir), canonicalPath, tusdMountPoint);
+    replaceChars(ret, tusdDataDir, tusdMountPoint);
     }
-return dataDir;
+return ret;
 }
 
 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() */
+ * link, or on a different filesystem. */
 {
 getDataDir(userName);
 if (!dataDir)
     {
     // catch a realpath error
     return NULL;
     }
 int prefixSize = strlen(dataDir);
 if (startsWith(dataDir, fname))
     {
     char *ret = fname + prefixSize;
     return ret;
     }
+// may be calling from a different server than the files are actually
+// residing, try swapping with hg.conf values
+char *mountedFilePath = swapDataDir(userName, fname);
+if (startsWith(dataDir, mountedFilePath))
+    {
+    int prefixSize = strlen(dataDir);
+    return mountedFilePath + prefixSize;
+    }
 return NULL;
 }
 
 char *getHubDataDir(char *userName, char *hub)
 {
 char *dataDir = getDataDir(userName);
 return catTwoStrings(dataDir, cgiEncode(hub));
 }
 
 char *hubSpaceUrl = NULL;
 static char *getHubSpaceUrl()
 {
 if (!hubSpaceUrl)
     hubSpaceUrl = cfgOption("hubSpaceUrl");
 return hubSpaceUrl;