ef8d1004e3d984933a79686a7369d74825886c48 max Fri Sep 4 11:35:31 2026 -0700 snapshotSession: reject a snapshot missing its required var A "blat" snapshot is a dead link without blatLastBigBed (the pinned bigPsl). That variable is set by an async hgc buildBigPsl call, so a share clicked before the build finished - or after it failed - would have minted a link that reopens to nothing. A snapshotType may now name a requiredVar, and doSaveSessionJson refuses the save (asking the caller to retry) instead of handing out a broken link. Found in code review of the BLAT share wiring. refs #38197 diff --git src/hg/lib/snapshotSession.c src/hg/lib/snapshotSession.c index 3eabd55d6ab..b85d37d18e8 100644 --- src/hg/lib/snapshotSession.c +++ src/hg/lib/snapshotSession.c @@ -17,45 +17,53 @@ #include "snapshotSession.h" /* Bits of randomness in a server-generated snapshot token. 128 bits -> ~24 URL-safe chars, so * collisions are astronomically unlikely; snapshotNewName() also checks the DB and retries, so the * name is guaranteed unique regardless. */ #define snapshotTokenBits 128 /* ---- Registry of snapshot types ------------------------------------------------------------- */ /* BLAT: a single alignment (or the hgBlat results table) rebuilds from just the pinned bigPsl file; * the query sequence lives inside the bigPsl, so no .fa/.pslx is needed. "db" is added implicitly. */ static char *blatVars[] = { "blatLastBigBed", NULL }; static struct snapshotType snapshotTypes[] = { - { "blat", blatVars }, + { "blat", blatVars, "blatLastBigBed" }, }; struct snapshotType *snapshotTypeFind(char *name) /* Return the registered snapshot type, or NULL if name is not a known type. */ { if (isEmpty(name)) return NULL; int i; for (i = 0; i < ArraySize(snapshotTypes); i++) if (sameString(name, snapshotTypes[i].name)) return &snapshotTypes[i]; return NULL; } +boolean snapshotHasRequired(struct snapshotType *type, struct cart *cart) +/* Return FALSE when type declares a requiredVar that is missing/empty in cart. */ +{ +if (type == NULL || isEmpty(type->requiredVar)) + return TRUE; +return isNotEmpty(cartOptionalString(cart, type->requiredVar)); +} + boolean snapshotIsSnapshotName(char *sessionName) /* Return TRUE if sessionName is a snapshot name (starts with the "__" prefix). */ { return sessionName != NULL && startsWith(snapshotNamePrefix, sessionName); } static char *snapshotSessionDir(char *sessionDataDir, char *encUserName, char *encSessionName) /* Alloc and return the durable data directory for one snapshot, or NULL if sessionDataDir is empty. * Like sessionData's sessionDirFromNames but with two extra hash levels drawn from the session name, * so a single high-volume user - the anonymous "l", which owns most snapshots - never accumulates * millions of entries in one directory. Layout: * sessionDataDir / <2hex md5(user)> / <encUser> / <2hex sess> / <2hex sess> / <8hex md5(session)> * Snapshots use their own layout (not sessionDirFromNames), so this never affects normal sessions. */ { if (isEmpty(sessionDataDir))