src/hg/lib/cart.c 1.110

1.110 2009/06/15 23:37:20 angie
Added stderr logging (a la htmlVaWarn) to cartEarlyWarningHandler, so we get an informative message along with the stack dump.
Index: src/hg/lib/cart.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/lib/cart.c,v
retrieving revision 1.109
retrieving revision 1.110
diff -b -B -U 1000000 -r1.109 -r1.110
--- src/hg/lib/cart.c	3 Jun 2009 04:30:19 -0000	1.109
+++ src/hg/lib/cart.c	15 Jun 2009 23:37:20 -0000	1.110
@@ -1,1766 +1,1775 @@
 #include "common.h"
 #include "hCommon.h"
 #include "obscure.h"
 #include "linefile.h"
 #include "errabort.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "cartDb.h"
 #include "htmshell.h"
 #include "hgConfig.h"
 #include "cart.h"
 #include "net.h"
 #include "web.h"
 #include "hdb.h"
 #include "jksql.h"
 #include "trashDir.h"
 #ifndef GBROWSE
 #include "customFactory.h"
 #include "googleAnalytics.h"
 #include "wikiLink.h"
 #endif /* GBROWSE */
 #include "hgMaf.h"
 #include "hui.h"
 
 static char const rcsid[] = "$Id$";
 
 static char *sessionVar = "hgsid";	/* Name of cgi variable session is stored in. */
 static char *positionCgiName = "position";
 
 DbConnector cartDefaultConnector = hConnectCart;
 DbDisconnect cartDefaultDisconnector = hDisconnectCart;
 
 static void hashUpdateDynamicVal(struct hash *hash, char *name, void *val)
 /* Val is a dynamically allocated (freeMem-able) entity to put
  * in hash.  Override existing hash item with that name if any.
  * Otherwise make new hash item. */
 {
 struct hashEl *hel = hashLookup(hash, name);
 if (hel == NULL)
     hashAdd(hash, name, val);
 else
     {
     freeMem(hel->val);
     hel->val = val;
     }
 }
 
 void cartTrace(struct cart *cart, char *when, struct sqlConnection *conn)
 /* Write some properties of the cart to stderr for debugging. */
 {
 if (cfgOption("cart.trace") == NULL)
     return;
 struct cartDb *u = cart->userInfo, *s = cart->sessionInfo;
 char *pix = hashFindVal(cart->hash, "pix");
 char *textSize = hashFindVal(cart->hash, "textSize");
 char *trackControls = hashFindVal(cart->hash, "trackControlsOnMain");
 int uLen, sLen;
 if (conn != NULL)
     {
     /* Since the content string is chopped, query for the actual length. */
     char query[1024];
     safef(query, sizeof(query), "select length(contents) from userDb "
 	  "where id = %d", u->id);
     uLen = sqlQuickNum(conn, query);
     safef(query, sizeof(query), "select length(contents) from sessionDb "
 	  "where id = %d", s->id);
     sLen = sqlQuickNum(conn, query);
     }
 else
     {
     uLen = strlen(u->contents);
     sLen = strlen(s->contents);
     }
 if (pix == NULL)
     pix = "-";
 if (textSize == NULL)
     textSize = "-";
 if (trackControls == NULL)
     trackControls = "-";
 fprintf(stderr, "ASH: %22s: "
 	"u.i=%d u.l=%d u.c=%d s.i=%d s.l=%d s.c=%d "
 	"p=%s f=%s t=%s pid=%ld %s\n",
 	when,
 	u->id, uLen, u->useCount, s->id, sLen, s->useCount,
 	pix, textSize, trackControls, (long)getpid(), cgiRemoteAddr());
 if (cart->userId != 0 && u->id != cart->userId)
     fprintf(stderr, "ASH: bad userId %d --> %d!  pid=%ld\n",
 	    cart->userId, u->id, (long)getpid());
 if (cart->sessionId != 0 && s->id != cart->sessionId)
     fprintf(stderr, "ASH: bad sessionId %d --> %d!  pid=%ld\n",
 	    cart->sessionId, s->id, (long)getpid());
 }
 
 boolean cartTablesOk(struct sqlConnection *conn)
 /* Return TRUE if cart tables are accessible (otherwise, the connection
  * doesn't do us any good). */
 {
 if (!sqlTableOk(conn, "userDb"))
     {
     fprintf(stderr, "ASH: cartTablesOk failed on %s.userDb!  pid=%ld\n",
 	    sqlGetDatabase(conn), (long)getpid());
     return FALSE;
     }
 if (!sqlTableOk(conn, "sessionDb"))
     {
     fprintf(stderr, "ASH: cartTablesOk failed on %s.sessionDb!  pid=%ld\n",
 	    sqlGetDatabase(conn), (long)getpid());
     return FALSE;
     }
 return TRUE;
 }
 
 static void cartParseOverHash(struct cart *cart, char *contents)
 /* Parse cgi-style contents into a hash table.  This will *not*
  * replace existing members of hash that have same name, so we can
  * support multi-select form inputs (same var name can have multiple
  * values which will be in separate hashEl's). */
 {
 struct hash *hash = cart->hash;
 char *namePt, *dataPt, *nextNamePt;
 namePt = contents;
 while (namePt != NULL && namePt[0] != 0)
     {
     dataPt = strchr(namePt, '=');
     if (dataPt == NULL)
 	errAbort("Mangled input string %s", namePt);
     *dataPt++ = 0;
     nextNamePt = strchr(dataPt, '&');
     if (nextNamePt == NULL)
 	nextNamePt = strchr(dataPt, ';');	/* Accomodate DAS. */
     if (nextNamePt != NULL)
          *nextNamePt++ = 0;
     cgiDecode(dataPt,dataPt,strlen(dataPt));
     hashAdd(hash, namePt, cloneString(dataPt));
     namePt = nextNamePt;
     }
 }
 
 static boolean looksCorrupted(struct cartDb *cdb)
 /* Test for db corruption by checking format of firstUse field. */
 {
 if (cdb == NULL)
     return FALSE;
 else
     {
     char *words[3];
     int wordCount = 0;
     boolean isCorr = FALSE;
     char *fu = cloneString(cdb->firstUse);
     wordCount = chopByChar(fu, '-', words, ArraySize(words));
     if (wordCount < 3)
 	isCorr = TRUE;
     else
 	{
 	time_t theTime = time(NULL);
 	struct tm *tm = localtime(&theTime);
 	int year = atoi(words[0]);
 	int month = atoi(words[1]);
 	if ((year < 2000) || (year > (1900+tm->tm_year)) ||
 	    (month < 1) || (month > 12))
 	    isCorr = TRUE;
 	}
     freez(&fu);
     return isCorr;
     }
 }
 
 struct cartDb *cartDbLoadFromId(struct sqlConnection *conn, char *table, int id)
 /* Load up cartDb entry for particular ID.  Returns NULL if no such id. */
 {
 if (id == 0)
    return NULL;
 else
    {
    struct cartDb *cdb = NULL;
    char where[64];
    safef(where, sizeof(where), "id = %u", id);
    cdb = cartDbLoadWhere(conn, table, where);
    if (looksCorrupted(cdb))
        {
        /* Can't use warn here -- it interrupts the HTML header, causing an
 	* err500 (and nothing useful in error_log) instead of a warning. */
        fprintf(stderr,
 	       "%s id=%d looks corrupted -- starting over with new %s id.\n",
 	       table, id, table);
        cdb = NULL;
        }
    return cdb;
    }
 }
 
 struct cartDb *loadDb(struct sqlConnection *conn, char *table, int id, boolean *found)
 /* Load bits from database and save in hash. */
 {
 struct cartDb *cdb;
 char query[256];
 boolean result = TRUE;
 
 cdb = cartDbLoadFromId(conn, table, id);
 if (!cdb)
     {
     result = FALSE;
     safef(query, sizeof(query), "INSERT %s VALUES(0,\"\",0,now(),now(),0)",
 	  table);
     sqlUpdate(conn, query);
     id = sqlLastAutoId(conn);
     if ((cdb = cartDbLoadFromId(conn,table,id)) == NULL)
         errAbort("Couldn't get cartDb for id=%d right after loading.  "
 		 "MySQL problem??", id);
     }
 *found = result;
 return cdb;
 }
 
 void cartExclude(struct cart *cart, char *var)
 /* Exclude var from persistent storage. */
 {
 hashAdd(cart->exclude, var, NULL);
 }
 
 
 void sessionTouchLastUse(struct sqlConnection *conn, char *encUserName,
 			 char *encSessionName)
 /* Increment namedSessionDb.useCount and update lastUse for this session. */
 {
 struct dyString *dy = dyStringNew(1024);
 int useCount;
 dyStringPrintf(dy, "SELECT useCount FROM %s "
 	       "WHERE userName = '%s' AND sessionName = '%s';",
 	       namedSessionTable, encUserName, encSessionName);
 useCount = sqlQuickNum(conn, dy->string) + 1;
 dyStringClear(dy);
 dyStringPrintf(dy, "UPDATE %s SET useCount = %d, lastUse=now() "
 	       "WHERE userName = '%s' AND sessionName = '%s';",
 	       namedSessionTable, useCount, encUserName, encSessionName);
 sqlUpdate(conn, dy->string);
 dyStringFree(&dy);
 }
 
 #ifndef GBROWSE
 static void cartCopyCustomTracks(struct cart *cart, struct hash *oldVars)
 /* If cart contains any live custom tracks, save off a new copy of them,
  * to prevent clashes by multiple loaders of the same session.  */
 {
 struct hashEl *el, *elList = hashElListHash(cart->hash);
 char *db=NULL, *ignored;
 getDbAndGenome(cart, &db, &ignored, oldVars);
 
 for (el = elList; el != NULL; el = el->next)
     {
     if (startsWith(CT_FILE_VAR_PREFIX, el->name))
 	{
 	struct slName *browserLines = NULL;
 	struct customTrack *ctList = NULL;
 	char *ctFileName = (char *)(el->val);
 	if (fileExists(ctFileName))
 	    ctList = customFactoryParseAnyDb(db, ctFileName, TRUE, &browserLines);
 	/* Save off only if the custom tracks are live -- if none are live,
 	 * leave cart variables in place so hgSession can detect and inform
 	 * the user. */
 	if (ctList)
 	    {
 	    struct customTrack *ct;
 	    static struct tempName tn;
 	    char *ctFileVar = el->name;
 	    char *ctFileName;
 	    for (ct = ctList;  ct != NULL;  ct = ct->next)
 		{
 		copyFileToTrash(&(ct->htmlFile), "ct", CT_PREFIX, ".html");
 		copyFileToTrash(&(ct->wibFile), "ct", CT_PREFIX, ".wib");
 		copyFileToTrash(&(ct->wigFile), "ct", CT_PREFIX, ".wig");
 		}
 	    trashDirFile(&tn, "ct", CT_PREFIX, ".bed");
 	    ctFileName = tn.forCgi;
 	    cartSetString(cart, ctFileVar, ctFileName);
 	    customTracksSaveFile(db, ctList, ctFileName);
 	    }
 	}
     }
 }
 #endif /* GBROWSE */
 
 static void storeInOldVars(struct cart *cart, struct hash *oldVars, char *var)
 /* Store all cart hash elements for var into oldVars (if it exists). */
 {
 if (oldVars == NULL)
     return;
 struct hashEl *hel = hashLookup(cart->hash, var);
 while (hel != NULL)
     {
     hashAdd(oldVars, var, cloneString(hel->val));
     hel = hashLookupNext(hel);
     }
 }
 
 static void loadCgiOverHash(struct cart *cart, struct hash *oldVars)
 /* Store CGI variables in cart. */
 {
 struct cgiVar *cv, *cvList = cgiVarList();
 char *booShadow = cgiBooleanShadowPrefix();
 int booSize = strlen(booShadow);
 char *multShadow = cgiMultListShadowPrefix();
 int multSize = strlen(multShadow);
 struct hash *booHash = newHash(8);
 struct hash *cgiHash = hashNew(11);
 
 /* First handle boolean variables and store in cgiHash.  We store in a
  * separate hash in order to distinguish between a list-variable's old
  * values in the cart hash and new values from cgi. */
 for (cv = cvList; cv != NULL; cv = cv->next)
     {
     if (startsWith(booShadow, cv->name))
         {
 	char *booVar = cv->name + booSize;
     // Support for 2 boolean CBs: checked/unchecked (1/0) and enabled/disabled:(-1/-2)
 	char *val = (cgiVarExists(booVar) ? "1" : cv->val);
 	storeInOldVars(cart, oldVars, booVar);
 	cartRemove(cart, booVar);
 	hashAdd(cgiHash, booVar, val);
 	hashAdd(booHash, booVar, NULL);
 	}
     else if (startsWith(multShadow, cv->name))
 	{
 	/* This shadow variable enables us to detect when all inputs in
 	 * the multi-select box have been deselected. */
 	char *multVar = cv->name + multSize;
 	if (! cgiVarExists(multVar))
 	    {
 	    storeInOldVars(cart, oldVars, multVar);
 	    cartRemove(cart, multVar);
 	    }
 	}
     }
 
 /* Handle non-boolean vars. */
 for (cv = cgiVarList(); cv != NULL; cv = cv->next)
     {
     if (! (startsWith(booShadow, cv->name) || hashLookup(booHash, cv->name) ||
 	   startsWith(multShadow, cv->name)) )
 
 	{
 	storeInOldVars(cart, oldVars, cv->name);
 	cartRemove(cart, cv->name);
 	hashAdd(cgiHash, cv->name, cv->val);
 	}
     }
 
 /* Add new settings to cart (old values of these variables have been
  * removed above). */
 struct hashEl *hel = hashElListHash(cgiHash);
 while (hel != NULL)
     {
     cartAddString(cart, hel->name, hel->val);
     hel = hel->next;
     }
 hashFree(&cgiHash);
 hashFree(&booHash);
 }
 
 static void hashEmpty(struct hash *hash)
 /* Remove everything from hash. */
 {
 struct hashEl *hel, *helList = hashElListHash(hash);
 for (hel = helList;  hel != NULL;  hel = hel->next)
     {
     freez(&(hel->val));
     hashRemove(hash, hel->name);
     }
 hashElFreeList(&helList);
 assert(hashNumEntries(hash) == 0);
 }
 
 #ifndef GBROWSE
 void cartLoadUserSession(struct sqlConnection *conn, char *sessionOwner,
 			 char *sessionName, struct cart *cart,
 			 struct hash *oldVars, char *actionVar)
 /* If permitted, load the contents of the given user's session, and then
  * reload the CGI settings (to support override of session settings).
  * If non-NULL, oldVars will contain values overloaded when reloading CGI.
  * If non-NULL, actionVar is a cartRemove wildcard string specifying the
  * CGI action variable that sent us here. */
 {
 struct sqlResult *sr = NULL;
 char **row = NULL;
 char *userName = wikiLinkUserName();
 char *encSessionName = cgiEncodeFull(sessionName);
 char *encSessionOwner = cgiEncodeFull(sessionOwner);
 char query[512];
 
 if (isEmpty(sessionOwner))
     errAbort("Please go back and enter a wiki user name for this session.");
 if (isEmpty(sessionName))
     errAbort("Please go back and enter a session name to load.");
 
 safef(query, sizeof(query), "SELECT shared, contents FROM %s "
       "WHERE userName = '%s' AND sessionName = '%s';",
       namedSessionTable, encSessionOwner, encSessionName);
 sr = sqlGetResult(conn, query);
 if ((row = sqlNextRow(sr)) != NULL)
     {
     boolean shared = atoi(row[0]);
     if (shared ||
 	(userName && sameString(sessionOwner, userName)))
 	{
 	char *sessionVar = cartSessionVarName();
 	unsigned hgsid = cartSessionId(cart);
 	struct sqlConnection *conn2 = hConnectCentral();
 	sessionTouchLastUse(conn2, encSessionOwner, encSessionName);
 	cartRemoveLike(cart, "*");
 	cartParseOverHash(cart, row[1]);
 	cartSetInt(cart, sessionVar, hgsid);
 	if (oldVars)
 	    hashEmpty(oldVars);
 	/* Overload settings explicitly passed in via CGI (except for the
 	 * command that sent us here): */
 	loadCgiOverHash(cart, oldVars);
 #ifndef GBROWSE
 	cartCopyCustomTracks(cart, oldVars);
 #endif /* GBROWSE */
 	if (isNotEmpty(actionVar))
 	    cartRemove(cart, actionVar);
 	hDisconnectCentral(&conn2);
 	}
     else
 	errAbort("Sharing has not been enabled for user %s's session %s.",
 		 sessionOwner, sessionName);
     }
 else
     errAbort("Could not find session %s for user %s.",
 	     sessionName, sessionOwner);
 sqlFreeResult(&sr);
 freeMem(encSessionName);
 }
 #endif /* GBROWSE */
 
 void cartLoadSettings(struct lineFile *lf, struct cart *cart,
 		      struct hash *oldVars, char *actionVar)
 /* Load settings (cartDump output) into current session, and then
  * reload the CGI settings (to support override of session settings).
  * If non-NULL, oldVars will contain values overloaded when reloading CGI.
  * If non-NULL, actionVar is a cartRemove wildcard string specifying the
  * CGI action variable that sent us here. */
 {
 char *line = NULL;
 int size = 0;
 char *sessionVar = cartSessionVarName();
 unsigned hgsid = cartSessionId(cart);
 
 cartRemoveLike(cart, "*");
 cartSetInt(cart, sessionVar, hgsid);
 while (lineFileNext(lf, &line, &size))
     {
     char *var = nextWord(&line);
     char *val = line;
 
     if (sameString(var, sessionVar))
 	continue;
     else
 	{
 	if (val != NULL)
 	    {
 	    struct dyString *dy = dyStringSub(val, "\\n", "\n");
 	    cartAddString(cart, var, dy->string);
 	    dyStringFree(&dy);
 	    }
 	else if (var != NULL)
 	    {
 	    cartSetString(cart, var, "");
 	    }
 	} /* not hgsid */
     } /* each line */
 if (oldVars)
     hashEmpty(oldVars);
 /* Overload settings explicitly passed in via CGI (except for the
  * command that sent us here): */
 loadCgiOverHash(cart, oldVars);
 #ifndef GBROWSE
 cartCopyCustomTracks(cart, oldVars);
 #endif /* GBROWSE */
 
 if (isNotEmpty(actionVar))
     cartRemove(cart, actionVar);
 }
 
 char *_cartVarDbName(char *db, char *var)
 /* generate cart variable name that is local to an assembly database.
  * Only for use inside of cart.h.  WARNING: static return */
 {
 static char buf[PATH_LEN]; // something rather big
 safef(buf, sizeof(buf), "%s_%s", var, db);
 return buf;
 }
 
 static char *now()
 /* Return a mysql-formatted time like "2008-05-19 15:33:34". */
 {
 char nowBuf[256];
 time_t seconds = clock1();
 struct tm *theTime = localtime(&seconds);
 strftime(nowBuf, sizeof nowBuf, "%Y-%m-%d %H:%M:%S", theTime);
 return cloneString(nowBuf);
 }
 
 static struct cartDb *emptyCartDb()
 /* Create a new empty placeholder cartDb. */
 {
 struct cartDb *cdb;
 AllocVar(cdb);
 cdb->contents = cloneString("");
 cdb->firstUse = now();
 cdb->lastUse = now();
 cdb->useCount = 1;
 return cdb;
 }
 
 struct cart *cartNewEmpty(unsigned int userId, unsigned int sessionId,
 	char **exclude, struct hash *oldVars)
 /* Create a new empty cart structure without reading from the database. */
 {
 struct cart *cart;
 char *ex;
 
 AllocVar(cart);
 cart->hash = newHash(12);
 cart->exclude = newHash(7);
 cart->userId = userId;
 cart->sessionId = sessionId;
 cart->userInfo = emptyCartDb();
 cart->sessionInfo = emptyCartDb();
 
 loadCgiOverHash(cart, oldVars);
 
 if (exclude != NULL)
     {
     while ((ex = *exclude++))
 	cartExclude(cart, ex);
     }
 return cart;
 }
 
 struct cart *cartNew(unsigned int userId, unsigned int sessionId,
 	char **exclude, struct hash *oldVars)
 /* Load up cart from user & session id's.  Exclude is a null-terminated list of
  * strings to not include */
 {
 struct cart *cart;
 struct sqlConnection *conn = cartDefaultConnector();
 char *ex;
 boolean userIdFound = FALSE, sessionIdFound = FALSE;
 
 AllocVar(cart);
 cart->hash = newHash(12);
 cart->exclude = newHash(7);
 cart->userId = userId;
 cart->sessionId = sessionId;
 cart->userInfo = loadDb(conn, "userDb", userId, &userIdFound);
 cart->sessionInfo = loadDb(conn, "sessionDb", sessionId, &sessionIdFound);
 if (sessionIdFound)
     cartParseOverHash(cart, cart->sessionInfo->contents);
 else if (userIdFound)
     cartParseOverHash(cart, cart->userInfo->contents);
 char when[1024];
 safef(when, sizeof(when), "open %d %d", userId, sessionId);
 cartTrace(cart, when, conn);
 
 loadCgiOverHash(cart, oldVars);
 
 #ifndef GBROWSE
 /* If some CGI other than hgSession been passed hgSession loading instructions,
  * apply those to cart before we do anything else.  (If this is hgSession,
  * let it handle the settings so it can display feedback to the user.) */
 if (! (cgiScriptName() && endsWith(cgiScriptName(), "hgSession")))
     {
     if (cartVarExists(cart, hgsDoOtherUser))
 	{
 	char *otherUser = cartString(cart, hgsOtherUserName);
 	char *sessionName = cartString(cart, hgsOtherUserSessionName);
 	struct sqlConnection *conn2 = hConnectCentral();
 	cartLoadUserSession(conn2, otherUser, sessionName, cart,
 			    oldVars, hgsDoOtherUser);
 	hDisconnectCentral(&conn2);
 	cartTrace(cart, "after cartLUS", conn);
 	}
     else if (cartVarExists(cart, hgsDoLoadUrl))
 	{
 	char *url = cartString(cart, hgsLoadUrlName);
 	struct lineFile *lf = netLineFileOpen(url);
 	cartLoadSettings(lf, cart, oldVars, hgsDoLoadUrl);
 	lineFileClose(&lf);
 	cartTrace(cart, "after cartLS", conn);
 	}
     }
 #endif /* GBROWSE */
 
 if (exclude != NULL)
     {
     while ((ex = *exclude++))
 	cartExclude(cart, ex);
     }
 
 cartDefaultDisconnector(&conn);
 return cart;
 }
 
 
 
 static void updateOne(struct sqlConnection *conn,
 	char *table, struct cartDb *cdb, char *contents, int contentSize)
 /* Update cdb in database. */
 {
 struct dyString *dy = newDyString(4096);
 dyStringPrintf(dy, "UPDATE %s SET contents='", table);
 dyStringAppendN(dy, contents, contentSize);
 dyStringPrintf(dy, "',lastUse=now(),useCount=%d ", cdb->useCount+1);
 dyStringPrintf(dy, " where id=%u", cdb->id);
 sqlUpdate(conn, dy->string);
 dyStringFree(&dy);
 }
 
 
 void cartEncodeState(struct cart *cart, struct dyString *dy)
 /* Add a CGI-encoded var=val&... string of all cart variables to dy. */
 {
 struct hashEl *el, *elList = hashElListHash(cart->hash);
 boolean firstTime = TRUE;
 char *s = NULL;
 for (el = elList; el != NULL; el = el->next)
     {
     if (!hashLookup(cart->exclude, el->name))
 	{
 	if (firstTime)
 	    firstTime = FALSE;
 	else
 	    dyStringAppendC(dy, '&');
 	dyStringAppend(dy, el->name);
 	dyStringAppendC(dy, '=');
 	s = cgiEncode(el->val);
 	dyStringAppend(dy, s);
 	freez(&s);
 	}
     }
 hashElFreeList(&elList);
 }
 
 static void saveState(struct cart *cart)
 /* Save out state to permanent storage in both user and session db. */
 {
 struct sqlConnection *conn = cartDefaultConnector();
 struct dyString *encoded = newDyString(4096);
 
 /* Make up encoded string holding all variables. */
 cartEncodeState(cart, encoded);
 
 /* Make up update statement unless it looks like a robot with
  * a great bunch of variables. */
 if (encoded->stringSize < 16*1024 || cart->userInfo->useCount > 0)
     {
     updateOne(conn, "userDb", cart->userInfo, encoded->string, encoded->stringSize);
     updateOne(conn, "sessionDb", cart->sessionInfo, encoded->string, encoded->stringSize);
     }
 else
     {
     fprintf(stderr, "Cart stuffing bot?  Not writing %d bytes to cart on first use of %d from IP=%s\n",
     	encoded->stringSize, cart->userInfo->id, cgiRemoteAddr());
     /* Do increment the useCount so that cookie-users don't get stuck here: */
     updateOne(conn, "userDb", cart->userInfo, "", 0);
     }
 
 /* Cleanup */
 cartDefaultDisconnector(&conn);
 dyStringFree(&encoded);
 }
 
 void cartCheckout(struct cart **pCart)
 /* Free up cart and save it to database. */
 {
 struct cart *cart = *pCart;
 if (cart != NULL)
     {
     saveState(cart);
     struct sqlConnection *conn = cartDefaultConnector();
     cartTrace(cart, "checkout", conn);
     cartDefaultDisconnector(&conn);
     cartDbFree(&cart->userInfo);
     cartDbFree(&cart->sessionInfo);
     freeHash(&cart->hash);
     freeHash(&cart->exclude);
     freez(pCart);
     }
 }
 
 char *cartSessionVarName()
 /* Return name of CGI session ID variable. */
 {
 return sessionVar;
 }
 
 unsigned int cartSessionId(struct cart *cart)
 /* Return session id. */
 {
 return cart->sessionInfo->id;
 }
 
 char *cartSidUrlString(struct cart *cart)
 /* Return session id string as in hgsid=N . */
 {
 static char buf[64];
 safef(buf, sizeof(buf), "%s=%u", cartSessionVarName(), cartSessionId(cart));
 return buf;
 }
 
 unsigned int cartUserId(struct cart *cart)
 /* Return session id. */
 {
 return cart->userInfo->id;
 }
 
 void cartRemove(struct cart *cart, char *var)
 /* Remove variable from cart. */
 {
 struct hashEl *hel = hashLookup(cart->hash, var);
 while (hel != NULL)
     {
     struct hashEl *nextHel = hashLookupNext(hel);
     freez(&hel->val);
     hashRemove(cart->hash, var);
     hel = nextHel;
     }
 }
 
 void cartRemoveExcept(struct cart *cart, char **except)
 /* Remove variables except those in null terminated except array
  * from cart.  Except array may be NULL in which case all
  * are removed. */
 {
 struct hash *exceptHash = newHash(10);
 struct hashEl *list = NULL, *el;
 char *s;
 
 /* Build up hash of things to exclude. */
 if (except != NULL)
     {
     while ((s = *except++) != NULL)
 	hashAdd(exceptHash, s, NULL);
     }
 
 /* Get all cart variables and remove most of them. */
 list = hashElListHash(cart->hash);
 for (el = list; el != NULL; el = el->next)
     {
     if (!hashLookup(exceptHash, el->name))
 	cartRemove(cart, el->name);
     }
 
 /* Clean up. */
 hashFree(&exceptHash);
 hashElFreeList(&list);
 }
 
 void cartRemoveLike(struct cart *cart, char *wildCard)
 /* Remove all variable from cart that match wildCard. */
 {
 struct hashEl *el, *elList = hashElListHash(cart->hash);
 
 slSort(&elList, hashElCmp);
 for (el = elList; el != NULL; el = el->next)
     {
     if (wildMatch(wildCard, el->name))
 	cartRemove(cart, el->name);
     }
 hashElFreeList(&el);
 }
 
 void cartRemovePrefix(struct cart *cart, char *prefix)
 /* Remove variables with given prefix from cart. */
 {
 struct hashEl *el, *elList = hashElListHash(cart->hash);
 
 slSort(&elList, hashElCmp);
 for (el = elList; el != NULL; el = el->next)
     {
     if (startsWith(prefix, el->name))
 	cartRemove(cart, el->name);
     }
 hashElFreeList(&el);
 }
 
 boolean cartVarExists(struct cart *cart, char *var)
 /* Return TRUE if variable is in cart. */
 {
 return hashFindVal(cart->hash, var) != NULL;
 }
 
 char *cartString(struct cart *cart, char *var)
 /* Return string valued cart variable. */
 {
 return hashMustFindVal(cart->hash, var);
 }
 
 char *cartOptionalString(struct cart *cart, char *var)
 /* Return string valued cart variable or NULL if it doesn't exist. */
 {
 return hashFindVal(cart->hash, var);
 }
 
 char *cartNonemptyString(struct cart *cart, char *name)
 /* Return string value associated with name.  Return NULL
  * if value doesn't exist or if it is pure white space. */
 {
 char *val = trimSpaces(cartOptionalString(cart, name));
 if (val != NULL && val[0] == 0)
     val = NULL;
 return val;
 }
 
 char *cartUsualString(struct cart *cart, char *var, char *usual)
 /* Return variable value if it exists or usual if not. */
 {
 char *s = cartOptionalString(cart, var);
 if (s == NULL)
     return usual;
 return s;
 }
 
 char *cartCgiUsualString(struct cart *cart, char *var, char *usual)
 /* Look for var in CGI, then in cart, if not found then return usual. */
 {
 char *val = cgiOptionalString(var);
 if (val != NULL)
     return(val);
 if (cart != NULL)
     return cartUsualString(cart, var, usual);
 return(usual);
 }
 
 struct slName *cartOptionalSlNameList(struct cart *cart, char *var)
 /* Return slName list (possibly with multiple values for the same var) or
  * NULL if not found. */
 {
 struct slName *slnList = NULL;
 struct hashEl *hel = hashLookup(cart->hash, var);
 while (hel != NULL)
     {
     if (hel->val != NULL)
 	{
 	struct slName *sln = slNameNew(hel->val);
 	slAddHead(&slnList, sln);
 	}
     hel = hashLookupNext(hel);
     }
 return slnList;
 }
 
 void cartAddString(struct cart *cart, char *var, char *val)
 /* Add string valued cart variable (if called multiple times on same var,
  * will create a list -- retrieve with cartOptionalSlNameList. */
 {
 hashAdd(cart->hash, var, cloneString(val));
 }
 
 void cartSetString(struct cart *cart, char *var, char *val)
 /* Set string valued cart variable. */
 {
 hashUpdateDynamicVal(cart->hash, var, cloneString(val));
 }
 
 
 int cartInt(struct cart *cart, char *var)
 /* Return int valued variable. */
 {
 char *s = cartString(cart, var);
 return atoi(s);
 }
 
 int cartIntExp(struct cart *cart, char *var)
 /* Return integer valued expression in variable. */
 {
 return intExp(cartString(cart, var));
 }
 
 int cartUsualInt(struct cart *cart, char *var, int usual)
 /* Return variable value if it exists or usual if not. */
 {
 char *s = cartOptionalString(cart, var);
 if (s == NULL)
     return usual;
 return atoi(s);
 }
 
 int cartUsualIntClipped(struct cart *cart, char *var, int usual,
 	int minVal, int maxVal)
 /* Return integer variable clipped to lie between minVal/maxVal */
 {
 int val = cartUsualInt(cart, var, usual);
 if (val < minVal) val = minVal;
 if (val > maxVal) val = maxVal;
 return val;
 }
 
 int cartCgiUsualInt(struct cart *cart, char *var, int usual)
 /* Look for var in CGI, then in cart, if not found then return usual. */
 {
 char *val = cgiOptionalString(var);
 if (val != NULL)
     return atoi(val);
 if (cart != NULL)
     return cartUsualInt(cart, var, usual);
 return(usual);
 }
 
 void cartSetInt(struct cart *cart, char *var, int val)
 /* Set integer value. */
 {
 char buf[32];
 safef(buf, sizeof(buf), "%d", val);
 cartSetString(cart, var, buf);
 }
 
 double cartDouble(struct cart *cart, char *var)
 /* Return double valued variable. */
 {
 char *s = cartString(cart, var);
 return atof(s);
 }
 
 double cartUsualDouble(struct cart *cart, char *var, double usual)
 /* Return variable value if it exists or usual if not. */
 {
 char *s = cartOptionalString(cart, var);
 if (s == NULL)
     return usual;
 return atof(s);
 }
 
 double cartCgiUsualDouble(struct cart *cart, char *var, double usual)
 /* Look for var in CGI, then in cart, if not found then return usual. */
 {
 char *val = cgiOptionalString(var);
 if (val != NULL)
     return atof(val);
 if (cart != NULL)
     return cartUsualDouble(cart, var, usual);
 return(usual);
 }
 
 void cartSetDouble(struct cart *cart, char *var, double val)
 /* Set double value. */
 {
 char buf[32];
 safef(buf, sizeof(buf), "%f", val);
 cartSetString(cart, var, buf);
 }
 
 boolean cartBoolean(struct cart *cart, char *var)
 /* Retrieve cart boolean. */
 {
 char *s = cartString(cart, var);
 if (sameString(s, "on") || atoi(s) > 0)
     return TRUE;
 else
     return FALSE;
 }
 
 boolean cartUsualBoolean(struct cart *cart, char *var, boolean usual)
 /* Return variable value if it exists or usual if not. */
 {
 char *s = cartOptionalString(cart, var);
 if (s == NULL)
     return usual;
 return (sameString(s, "on") || atoi(s) > 0);
 }
 
 boolean cartCgiUsualBoolean(struct cart *cart, char *var, boolean usual)
 /* Look for var in CGI, then in cart, if not found then return usual. */
 {
 if (cgiBooleanDefined(var))
     return cgiBoolean(var);
 if (cart != NULL)
     return cgiBoolean(var) || cartUsualBoolean(cart, var, usual);
 return(usual);
 }
 
 void cartSetBoolean(struct cart *cart, char *var, boolean val)
 /* Set boolean value. */
 {
 cartSetInt(cart,var,(val?1:0)); // Be explicit because some cartBools overloaded with negative "disabled" values
 }
 
 void cartMakeTextVar(struct cart *cart, char *var, char *defaultVal, int charSize)
 /* Make a text control filled with value from cart if it exists or
  * default value otherwise.  If charSize is zero it's calculated to fit
  * current value.  Default value may be NULL. */
 {
 cgiMakeTextVar(var, cartUsualString(cart, var, defaultVal), charSize);
 }
 
 void cartMakeIntVar(struct cart *cart, char *var, int defaultVal, int maxDigits)
 /* Make a text control filled with integer value - from cart if available
  * otherwise default.  */
 {
 cgiMakeIntVar(var, cartUsualInt(cart, var, defaultVal), maxDigits);
 }
 
 void cartMakeDoubleVar(struct cart *cart, char *var, double defaultVal, int maxDigits)
 /* Make a text control filled with integer value - from cart if available
  * otherwise default.  */
 {
 cgiMakeDoubleVar(var, cartUsualDouble(cart, var, defaultVal), maxDigits);
 }
 
 void cartMakeCheckBox(struct cart *cart, char *var, boolean defaultVal)
 /* Make a check box filled with value from cart if it exists or
  * default value otherwise.  */
 {
 cgiMakeCheckBox(var, cartUsualBoolean(cart, var, defaultVal));
 }
 
 void cartMakeRadioButton(struct cart *cart, char *var, char *val, char *defaultVal)
 /* Make a radio button that is selected if cart variable exists and matches
  * value (or value matches default val if cart var doesn't exist). */
 {
 boolean matches = sameString(val, cartUsualString(cart, var, defaultVal));
 cgiMakeRadioButton(var, val, matches);
 }
 
 void cartSaveSession(struct cart *cart)
 /* Save session in a hidden variable. This needs to be called
  * somewhere inside of form or bad things will happen when user
  * has multiple windows open. */
 {
 char buf[64];
 safef(buf, sizeof(buf), "%u", cart->sessionInfo->id);
 cgiMakeHiddenVar(sessionVar, buf);
 }
 
 static void cartDumpItem(struct hashEl *hel)
 /* Dump one item in cart hash */
 {
 struct dyString *dy = NULL;
 char *val = (char *)(hel->val);
 stripChar(val, '\r');
 dy = dyStringSub(val, "\n", "\\n");
 printf("%s %s\n", hel->name, dy->string);
 dyStringFree(&dy);
 }
 
 void cartDumpList(struct hashEl *elList)
 /* Dump list of cart variables. */
 {
 struct hashEl *el;
 
 if (elList == NULL)
     return;
 slSort(&elList, hashElCmp);
 for (el = elList; el != NULL; el = el->next)
     cartDumpItem(el);
 hashElFreeList(&elList);
 }
 
 void cartDump(struct cart *cart)
 /* Dump contents of cart. */
 {
 struct hashEl *elList = hashElListHash(cart->hash);
 cartDumpList(elList);
 }
 
 void cartDumpPrefix(struct cart *cart, char *prefix)
 /* Dump all cart variables with prefix */
 {
 struct hashEl *elList = cartFindPrefix(cart, prefix);
 cartDumpList(elList);
 }
 
 void cartDumpLike(struct cart *cart, char *wildcard)
 /* Dump all cart variables matching wildcard */
 {
 struct hashEl *elList = cartFindLike(cart, wildcard);
 cartDumpList(elList);
 }
 
 char *cartFindFirstLike(struct cart *cart, char *wildCard)
 /* Find name of first variable that matches wildCard in cart.
  * Return NULL if none. */
 {
 struct hashEl *el, *elList = hashElListHash(cart->hash);
 char *name = NULL;
 
 for (el = elList; el != NULL; el = el->next)
     {
     if (wildMatch(wildCard, el->name))
 	{
 	name = el->name;
 	break;
 	}
     }
 hashElFreeList(&el);
 return name;
 }
 
 static struct hashEl *cartFindSome(struct cart *cart, char *pattern,
 	boolean (*match)(char *a, char *b))
 /* Return list of name/val pairs from cart where name matches
  * pattern.  Free when done with hashElFreeList. */
 {
 struct hashEl *el, *next, *elList = hashElListHash(cart->hash);
 struct hashEl *outList = NULL;
 
 for (el = elList; el != NULL; el = next)
     {
     next = el->next;
     if (match(pattern, el->name))
 	{
 	slAddHead(&outList, el);
 	}
     else
         {
 	hashElFree(&el);
 	}
     }
 return outList;
 }
 
 struct hashEl *cartFindLike(struct cart *cart, char *wildCard)
 /* Return list of name/val pairs from cart where name matches
  * wildcard.  Free when done with hashElFreeList. */
 {
 return cartFindSome(cart, wildCard, wildMatch);
 }
 
 struct hashEl *cartFindPrefix(struct cart *cart, char *prefix)
 /* Return list of name/val pairs from cart where name starts with
  * prefix.  Free when done with hashElFreeList. */
 {
 return cartFindSome(cart, prefix, startsWith);
 }
 
 
 static char *cookieDate()
 /* Return date string for cookie format.   We'll have to
  * revisit this in 35 years.... */
 {
 return "Thu, 31-Dec-2037 23:59:59 GMT";
 }
 
 static int getCookieId(char *cookieName)
 /* Get id value from cookie. */
 {
 char *hguidString = findCookieData(cookieName);
 return (hguidString == NULL ? 0 : atoi(hguidString));
 }
 
 static int getSessionId()
 /* Get session id if any from CGI. */
 {
 return cgiUsualInt("hgsid", 0);
 }
 
 static void clearDbContents(struct sqlConnection *conn, char *table, unsigned id)
 /* Clear out contents field of row in table that matches id. */
 {
 char query[256];
 if (id == 0)
    return;
 safef(query, sizeof(query), "update %s set contents='' where id=%u",
       table, id);
 sqlUpdate(conn, query);
 }
 
 void cartResetInDb(char *cookieName)
 /* Clear cart in database. */
 {
 int hguid = getCookieId(cookieName);
 int hgsid = getSessionId();
 struct sqlConnection *conn = cartDefaultConnector();
 clearDbContents(conn, "userDb", hguid);
 clearDbContents(conn, "sessionDb", hgsid);
 cartDefaultDisconnector(&conn);
 }
 
 void cartWriteCookie(struct cart *cart, char *cookieName)
 /* Write out HTTP Set-Cookie statement for cart. */
 {
 printf("Set-Cookie: %s=%u; path=/; domain=%s; expires=%s\r\n",
 	cookieName, cart->userInfo->id, cfgVal("central.domain"), cookieDate());
 }
 
 struct cart *cartForSession(char *cookieName, char **exclude,
 	struct hash *oldVars)
 /* This gets the cart without writing any HTTP lines at all to stdout. */
 {
 int hguid = getCookieId(cookieName);
 int hgsid = getSessionId();
 struct cart *cart = cartNew(hguid, hgsid, exclude, oldVars);
 cartExclude(cart, sessionVar);
 if (sameOk(cfgOption("signalsHandler"), "on"))  /* most cgis call this routine */
     initSigHandlers(webDumpStackEnabled());
 return cart;
 }
 
 struct cart *cartAndCookieWithHtml(char *cookieName, char **exclude,
 	struct hash *oldVars, boolean doContentType)
 /* Load cart from cookie and session cgi variable.  Write cookie
  * and optionally content-type part HTTP preamble to web page.  Don't
  * write any HTML though. */
 {
 if (doContentType)
     htmlPushEarlyHandlers();
 else
     pushWarnHandler(cartEarlyWarningHandler);
 struct cart *cart = cartForSession(cookieName, exclude, oldVars);
 popWarnHandler();
 cartWriteCookie(cart, cookieName);
 if (doContentType)
     {
     puts("Content-Type:text/html");
     puts("\n");
     }
 return cart;
 }
 
 struct cart *cartAndCookie(char *cookieName, char **exclude,
 	struct hash *oldVars)
 /* Load cart from cookie and session cgi variable.  Write cookie and
  * content-type part HTTP preamble to web page.  Don't write any HTML though. */
 {
 return cartAndCookieWithHtml(cookieName, exclude, oldVars, TRUE);
 }
 
 struct cart *cartAndCookieNoContent(char *cookieName, char **exclude,
 	struct hash *oldVars)
 /* Load cart from cookie and session cgi variable. Don't write out
  * content type or any HTML. */
 {
 return cartAndCookieWithHtml(cookieName, exclude, oldVars, FALSE);
 }
 
 static void cartErrorCatcher(void (*doMiddle)(struct cart *cart),
 	struct cart *cart)
 /* Wrap error catcher around call to do middle. */
 {
 int status = setjmp(htmlRecover);
 pushAbortHandler(htmlAbort);
 webDumpStackPushAbortHandler();
 if (status == 0)
     doMiddle(cart);
 webDumpStackPopAbortHandler();
 popAbortHandler();
 }
 
 void cartEarlyWarningHandler(char *format, va_list args)
 /* Write an error message so user can see it before page is really started. */
 {
 static boolean initted = FALSE;
+va_list argscp;
+va_copy(argscp, args);
 if (!initted)
     {
     htmStart(stdout, "Early Error");
     initted = TRUE;
     }
 printf("%s", htmlWarnStartPattern());
 htmlVaParagraph(format,args);
 printf("%s", htmlWarnEndPattern());
+
+/* write warning/error message to stderr so they get logged. */
+logCgiToStderr();
+vfprintf(stderr, format, argscp);
+va_end(argscp);
+putc('\n', stderr);
+fflush(stderr);
 }
 
 void cartWarnCatcher(void (*doMiddle)(struct cart *cart), struct cart *cart, WarnHandler warner)
 /* Wrap error and warning handlers around doMiddle. */
 {
 pushWarnHandler(warner);
 cartErrorCatcher(doMiddle, cart);
 popWarnHandler();
 }
 
 static boolean inWeb = FALSE;
 
 void cartHtmlStart(char *title)
 /* Write HTML header and put in normal error handler. */
 {
 pushWarnHandler(htmlVaWarn);
 htmStart(stdout, title);
 }
 
 void cartVaWebStart(struct cart *cart, char *db, char *format, va_list args)
 /* Print out pretty wrapper around things when working
  * from cart. */
 {
 pushWarnHandler(htmlVaWarn);
 webStartWrapper(cart, db, format, args, FALSE, FALSE);
 inWeb = TRUE;
 }
 
 void cartWebStart(struct cart *cart, char *db, char *format, ...)
 /* Print out pretty wrapper around things when working
  * from cart. */
 {
 va_list args;
 va_start(args, format);
 cartVaWebStart(cart, db, format, args);
 va_end(args);
 }
 
 void cartWebEnd()
 /* Write out HTML footer and get rid or error handler. */
 {
 webEnd();
 popWarnHandler();
 }
 
 void cartFooter(void)
 /* Write out HTML footer, possibly with googleAnalytics too */
 {
 #ifndef GBROWSE
 googleAnalytics();	/* can't do this in htmlEnd	*/
 #endif /* GBROWSE */
 htmlEnd();		/* because it is in a higher library */
 }
 
 void cartHtmlEnd()
 /* Write out HTML footer and get rid or error handler. */
 {
 if (inWeb)
     webEnd();	/*	this does googleAnalytics for a lot of CGIs	*/
 else
     cartFooter();
 popWarnHandler();
 }
 
 void cartEmptyShell(void (*doMiddle)(struct cart *cart), char *cookieName,
 	char **exclude, struct hash *oldVars)
 /* Get cart and cookies and set up error handling, but don't start writing any
  * html yet. The doMiddleFunction has to call cartHtmlStart(title), and
  * cartHtmlEnd(), as well as writing the body of the HTML.
  * oldVars - those in cart that are overlayed by cgi-vars are
  * put in optional hash oldVars. */
 {
 struct cart *cart = cartAndCookie(cookieName, exclude, oldVars);
 cartWarnCatcher(doMiddle, cart, cartEarlyWarningHandler);
 cartCheckout(&cart);
 }
 
 void cartHtmlShellPB(char *title, void (*doMiddle)(struct cart *cart),
         char *cookieName, char **exclude, struct hash *oldVars)
 /* For Proteome Browser, Load cart from cookie and session cgi variable.  Write web-page
  * preamble, call doMiddle with cart, and write end of web-page.
  * Exclude may be NULL.  If it exists it's a comma-separated list of
  * variables that you don't want to save in the cart between
  * invocations of the cgi-script. */
 {
 struct cart *cart;
 char *db, *org;
 char titlePlus[128];
 char *proteinID;
 pushWarnHandler(cartEarlyWarningHandler);
 cart = cartAndCookie(cookieName, exclude, oldVars);
 getDbAndGenome(cart, &db, &org, oldVars);
 proteinID = cartOptionalString(cart, "proteinID");
 safef(titlePlus, sizeof(titlePlus), "%s protein %s - %s", org, proteinID, title);
 popWarnHandler();
 htmStart(stdout, titlePlus);
 cartWarnCatcher(doMiddle, cart, htmlVaWarn);
 cartCheckout(&cart);
 cartFooter();
 }
 
 void cartHtmlShellPbGlobal(char *title, void (*doMiddle)(struct cart *cart),
         char *cookieName, char **exclude, struct hash *oldVars)
 /* For Proteome Browser, Load cart from cookie and session cgi variable.  Write web-page
  * preamble, call doMiddle with cart, and write end of web-page.
  * Exclude may be NULL.  If it exists it's a comma-separated list of
  * variables that you don't want to save in the cart between
  * invocations of the cgi-script. */
 /* cartHtmlShellPbGloabl differs from cartHtmlShellPB that it does not call getDbAndGenome */
 {
 struct cart *cart;
 char titlePlus[128];
 char *proteinID;
 pushWarnHandler(cartEarlyWarningHandler);
 cart = cartAndCookie(cookieName, exclude, oldVars);
 proteinID = cartOptionalString(cart, "proteinID");
 safef(titlePlus, sizeof(titlePlus), "Protein %s - %s", proteinID, title);
 popWarnHandler();
 htmStart(stdout, titlePlus);
 cartWarnCatcher(doMiddle, cart, htmlVaWarn);
 cartCheckout(&cart);
 cartFooter();
 }
 
 void cartHtmlShellWithHead(char *head, char *title, void (*doMiddle)(struct cart *cart),
 	char *cookieName, char **exclude, struct hash *oldVars)
 /* Load cart from cookie and session cgi variable.  Write web-page
  * preamble including head and title, call doMiddle with cart, and write end of web-page.
  * Exclude may be NULL.  If it exists it's a comma-separated list of
  * variables that you don't want to save in the cart between
  * invocations of the cgi-script. */
 {
 struct cart *cart;
 char *db, *org, *pos, *clade=NULL;
 char titlePlus[128];
 char extra[128];
 pushWarnHandler(cartEarlyWarningHandler);
 cart = cartAndCookie(cookieName, exclude, oldVars);
 getDbAndGenome(cart, &db, &org, oldVars);
 clade = hClade(org);
 pos = cartOptionalString(cart, positionCgiName);
 pos = addCommasToPos(db, stripCommas(pos));
 if(pos != NULL && oldVars != NULL)
     {
     struct hashEl *oldpos = hashLookup(oldVars, positionCgiName);
     if(oldpos != NULL && differentString(pos,oldpos->val))
         cartSetString(cart,"lastPosition",oldpos->val);
     }
 *extra = 0;
 if (pos == NULL && org != NULL)
     safef(titlePlus,sizeof(titlePlus), "%s%s - %s",org, extra, title );
 else if (pos != NULL && org == NULL)
     safef(titlePlus,sizeof(titlePlus), "%s - %s",pos, title );
 else if (pos == NULL && org == NULL)
     safef(titlePlus,sizeof(titlePlus), "%s", title );
 else
     safef(titlePlus,sizeof(titlePlus), "%s%s %s - %s",org, extra,pos, title );
 popWarnHandler();
 htmStartWithHead(stdout, head, titlePlus);
 cartWarnCatcher(doMiddle, cart, htmlVaWarn);
 cartCheckout(&cart);
 cartFooter();
 }
 
 void cartHtmlShell(char *title, void (*doMiddle)(struct cart *cart),
 	char *cookieName, char **exclude, struct hash *oldVars)
 /* Load cart from cookie and session cgi variable.  Write web-page
  * preamble, call doMiddle with cart, and write end of web-page.
  * Exclude may be NULL.  If it exists it's a comma-separated list of
  * variables that you don't want to save in the cart between
  * invocations of the cgi-script. */
 {
 cartHtmlShellWithHead("", title, doMiddle, cookieName, exclude, oldVars);
 }
 
 void cartSetDbConnector(DbConnector connector)
 /* Set the connector that will be used by the cart to connect to the
  * database. Default connector is hConnectCart */
 {
 cartDefaultConnector = connector;
 }
 
 void cartSetDbDisconnector(DbDisconnect disconnector)
 /* Set the connector that will be used by the cart to disconnect from the
  * database. Default disconnector is hDisconnectCart */
 {
 cartDefaultDisconnector = disconnector;
 }
 
 static void saveDefaultGsidLists(char *genomeDb, struct cart *cart)
 /* save the default lists of GSID subject and sequence IDs to 2 internal files under trash/ct
    for applications to use */
 {
 char *outName = NULL;
 char *outName2= NULL;
 FILE *outF, *outF2;
 struct tempName tn;
 struct tempName tn2;
 struct sqlResult *sr=NULL, *sr2=NULL;
 char **row, **row2;
 char query[255], query2[255];
 char *chp;
 struct sqlConnection *conn, *conn2;
 
 conn= hAllocConn(genomeDb);
 conn2= hAllocConn(genomeDb);
 
 trashDirFile(&tn, "ct", "gsidSubj", ".list");
 outName = tn.forCgi;
 
 trashDirFile(&tn2, "ct", "gsidSeq", ".list");
 outName2 = tn2.forCgi;
 
 outF = mustOpen(outName,"w");
 outF2= mustOpen(outName2,"w");
 
 safef(query, sizeof(query), "select distinct subjId from hgFixed.gsIdXref order by subjId");
 sr = sqlGetResult(conn, query);
 while ((row = sqlNextRow(sr)) != NULL)
     {
     fprintf(outF, "%s\n", row[0]);
 
     safef(query2, sizeof(query2),
           "select dnaSeqId from hgFixed.gsIdXref where subjId='%s' order by dnaSeqId", row[0]);
 
     sr2 = sqlGetResult(conn2, query2);
     while ((row2 = sqlNextRow(sr2)) != NULL)
         {
         /* Remove "ss." from the front of the DNA sequence ID,
            so that they could be used both for DNA and protein MSA maf display */
         chp = strstr(row2[0], "ss.");
         if (chp != NULL)
             {
             fprintf(outF2, "%s\t%s\n", chp+3L, row[0]);
             }
         else
             {
             fprintf(outF2, "%s\t%s\n", row2[0], row[0]);
             }
         }
     sqlFreeResult(&sr2);
     }
 
 sqlFreeResult(&sr);
 carefulClose(&outF);
 carefulClose(&outF2);
 hFreeConn(&conn);
 hFreeConn(&conn2);
 
 cartSetString(cart, gsidSubjList, outName);
 cartSetString(cart, gsidSeqList, outName2);
 }
 
 char *cartGetOrderFromFile(char *genomeDb, struct cart *cart, char *speciesUseFile)
 /* Look in a cart variable that holds the filename that has a list of
  * species to show in a maf file */
 {
 char *val;
 struct dyString *orderDY = dyStringNew(256);
 char *words[16];
 if ((val = cartUsualString(cart, speciesUseFile, NULL)) == NULL)
     {
     if (hIsGsidServer())
 	{
 	saveDefaultGsidLists(genomeDb, cart);
 	/* now it should be set */
 	val = cartUsualString(cart, speciesUseFile, NULL);
 	if (val == NULL)
     	    errAbort("can't find species list file var '%s' in cart\n",speciesUseFile);
 	}
     else
 	{
     	errAbort("can't find species list file var '%s' in cart\n",speciesUseFile);
 	}
     }
 
 struct lineFile *lf = lineFileOpen(val, TRUE);
 
 if (lf == NULL)
     errAbort("can't open species list file %s",val);
 
 while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) ))
     dyStringPrintf(orderDY, "%s ",words[0]);
 
 return dyStringCannibalize(&orderDY);
 }
 
 char *cartGetOrderFromFileAndMsaTable(char *genomeDb, struct cart *cart, char *speciesUseFile, char *msaTable)
 /* This function is used for GSID server only.
    Look in a cart variable that holds the filename that has a list of
  * species to show in a maf file and also restrict the results by the IDs existing in an MSA table*/
 {
 char *val;
 struct sqlResult *sr=NULL;
 char query[255];
 struct sqlConnection *conn;
 
 struct dyString *orderDY = dyStringNew(256);
 char *words[16];
 if ((val = cartUsualString(cart, speciesUseFile, NULL)) == NULL)
     {
     if (hIsGsidServer())
 	{
 	saveDefaultGsidLists(genomeDb, cart);
 
 	/* now it should be set */
 	val = cartUsualString(cart, speciesUseFile, NULL);
 	if (val == NULL)
     	    errAbort("can't find species list file var '%s' in cart\n",speciesUseFile);
 	}
     else
 	{
     	errAbort("can't find species list file var '%s' in cart\n",speciesUseFile);
 	}
     }
 
 struct lineFile *lf = lineFileOpen(val, TRUE);
 
 if (lf == NULL)
     errAbort("can't open species list file %s",val);
 
 if (hIsGsidServer())
     {
     conn= hAllocConn(genomeDb);
     while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) ))
     	{
 	safef(query, sizeof(query),
 	      "select id from %s where id like '%s%s'", msaTable, "%",  words[0]);
 	sr = sqlGetResult(conn, query);
 	if (sqlNextRow(sr) != NULL)
     	    {
     	    dyStringPrintf(orderDY, "%s ",words[0]);
 	    sqlFreeResult(&sr);
     	    }
   	}
     hFreeConn(&conn);
     }
 else
     {
     while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) ))
     	{
     	dyStringPrintf(orderDY, "%s ",words[0]);
     	}
     }
 return dyStringCannibalize(&orderDY);
 }
 
 char *cartLookUpVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix,char **pVariable)
 /* Returns value or NULL for a cart variable from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix
    Optionally fills the non NULL pVariable with the actual name of the variable in the cart */
 {
 char buf[512];
 safef(buf, sizeof buf, "%s.%s", tdb->tableName,suffix);
 
 char *cartSetting = NULL;
 if(!compositeLevel)
     cartSetting = hashFindVal(cart->hash, buf);
 if( cartSetting == NULL && tdbIsCompositeChild(tdb))
     {
     char *stView = NULL;
     if(subgroupFind(tdb,"view",&stView))
         {
         if(!compositeLevel)
             {
             safef(buf, sizeof buf, "%s.%s.%s", tdb->tableName,stView,suffix);
             cartSetting = hashFindVal(cart->hash, buf);
             }
         if(cartSetting == NULL)
             {
             safef(buf, sizeof buf, "%s.%s.%s", tdb->parent->tableName,stView,suffix);
             cartSetting = hashFindVal(cart->hash, buf);
             }
         }
     if(cartSetting == NULL)
         {
         safef(buf, sizeof buf, "%s.%s", tdb->parent->tableName,suffix);
         cartSetting = hashFindVal(cart->hash, buf);
         }
     }
 if(pVariable != NULL)
     {
     if(cartSetting != NULL)
         *pVariable = cloneString(buf);
     else
         *pVariable = NULL;
     }
 return cartSetting;
 }
 
 void cartRemoveVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix)
 /* Looks for then removes a cart variable from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *var = NULL;
 (void)cartLookUpVariableClosestToHome(cart,tdb,compositeLevel,suffix,&var);
 if(var != NULL)
     {
     cartRemove(cart,var);
     freeMem(var);
     }
 }
 
 char *cartStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix)
 /* Returns value or Aborts for a cart string from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix);
 if(setting == NULL)
     errAbort("cartStringClosestToHome: '%s' not found", suffix);
 return setting;
 }
 
 boolean cartVarExistsAnyLevel(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix)
 /* Returns TRUE if variable exists anywhere, looking from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 return (NULL != cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix));
 }
 
 
 char *cartUsualStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix, char *usual)
 /* Returns value or {usual} for a cart string from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix);
 if(setting == NULL)
     setting = usual;
 return setting;
 }
 
 boolean cartBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix)
 /* Returns value or Aborts for a cart boolean ('on' or != 0) from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartStringClosestToHome(cart,tdb,compositeLevel,suffix);
 return (sameString(setting, "on") || atoi(setting) > 0);
 }
 
 boolean cartUsualBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix,boolean usual)
 /* Returns value or {usual} for a cart boolean ('on' or != 0) from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix);
 if(setting == NULL)
     return usual;
 return (sameString(setting, "on") || atoi(setting) > 0);
 }
 
 
 int cartUsualIntClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix, int usual)
 /* Returns value or {usual} for a cart int from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix);
 if (setting == NULL)
     return usual;
 return atoi(setting);
 }
 
 double cartUsualDoubleClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix, double usual)
 /* Returns value or {usual} for a cart fp double from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *setting = cartOptionalStringClosestToHome(cart,tdb,compositeLevel,suffix);
 if (setting == NULL)
     return usual;
 return atof(setting);
 }
 
 struct slName *cartOptionalSlNameListClosestToHome(struct cart *cart, struct trackDb *tdb, boolean compositeLevel, char *suffix)
 /* Return slName list (possibly with multiple values for the same var) from lowest level on up:
    subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */
 {
 char *var = NULL;
 (void)cartLookUpVariableClosestToHome(cart,tdb,compositeLevel,suffix,&var);
 if(var == NULL)
     return NULL;
 
 struct slName *slNames = cartOptionalSlNameList(cart,var);
 freeMem(var);
 return slNames;
 }
 
 void cartRemoveAllForTdb(struct cart *cart, struct trackDb *tdb)
 /* Remove all variables from cart that are associated with this tdb. */
 {
 char setting[128];
 safef(setting,sizeof(setting),"%s.",tdb->tableName);
 cartRemovePrefix(cart,setting);
 safef(setting,sizeof(setting),"%s_",tdb->tableName); // TODO: All should be {tableName}.{varName}... Fix {tableName}_sel
 cartRemovePrefix(cart,setting);
 cartRemove(cart,tdb->tableName);
 }
 
 void cartRemoveAllForTdbAndChildren(struct cart *cart, struct trackDb *tdb)
 /* Remove all variables from cart that are associated
    with this tdb and it's children. */
 {
 cartRemoveAllForTdb(cart,tdb);
 struct trackDb *subTdb;
 for(subTdb=tdb->subtracks;subTdb!=NULL;subTdb=subTdb->next)
     cartRemoveAllForTdb(cart,subTdb);
 }