540506f7475055ae33969ce12d53a649ffff140e
braney
  Mon Apr 28 16:55:59 2025 -0700
change BAM support to use maxItems as a signal to go into coverage
mode. Added BAMMaxItems to limit the total number of items that might be retrieved from a BAM file.

diff --git src/hg/hgTracks/bamTrack.c src/hg/hgTracks/bamTrack.c
index fbfc138f815..01e718ba0e1 100644
--- src/hg/hgTracks/bamTrack.c
+++ src/hg/hgTracks/bamTrack.c
@@ -12,46 +12,66 @@
 #include "htmshell.h"
 #include "hui.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "cds.h"
 #include "hgBam.h"
 #include "wigCommon.h"
 #include "knetUdc.h"
 #include "udc.h"
 #include "bigWarn.h"
 #include "errCatch.h"
 #include "hgConfig.h"
 #include "chromAlias.h"
 
+#define TOOMANYITEMSERROR "Maximum number of BAM items exceeded. Perhaps zooming in a bit will help?"
 
 struct bamTrackData
     {
     struct track *tg;
     struct hash *pairHash;
     int minAliQual;
     char *colorMode;
     char *grayMode;
     char *userTag;
     int aliQualShadeMin;
     int aliQualShadeMax;
     int baseQualShadeMin;
     int baseQualShadeMax;
+    int count;
+    int maxItems;
     };
 
 
+static unsigned BAMMaxItems()
+/* Get the maximum number of items to grab from a BAM file.  Defaults to ten thousand . */
+{
+static boolean set = FALSE;
+static unsigned maxItems = 0;
+
+if (!set)
+    {
+    char *maxItemsStr = cfgOptionDefault("BAMMaxItems", "10000");
+
+    maxItems = sqlUnsigned(maxItemsStr);
+    set = TRUE;
+    }
+
+return maxItems;
+}
+
 static struct psl *pslFromBam(const bam1_t *bam)
 /* Translate BAM's numeric CIGAR encoding into PSL sufficient for cds.c (just coords,
  * no scoring info) */
 {
 const bam1_core_t *core = &bam->core;
 struct psl *psl;
 AllocVar(psl);
 boolean isRc = (core->flag & BAM_FREVERSE);
 psl->strand[0] = isRc ? '-' : '+';
 psl->qName = cloneString(bam1_qname(bam));
 psl->tName = cloneString(chromName);
 unsigned blockCount = 0;
 unsigned *blockSizes, *qStarts, *tStarts;
 AllocArray(blockSizes, core->n_cigar);
 AllocArray(qStarts, core->n_cigar);
@@ -291,42 +311,45 @@
 
 boolean passesFilters(const bam1_t *bam, struct bamTrackData *btd)
 /* Return TRUE if bam passes hgTrackUi-set filters. */
 {
 if (bam == NULL)
     return FALSE;
 const bam1_core_t *core = &bam->core;
 // Always reject unmapped items -- nowhere to draw them.
 if (core->flag & BAM_FUNMAP)
     return FALSE;
 if (core->qual < btd->minAliQual)
     return FALSE;
 return TRUE;
 }
 
-int addBam(const bam1_t *bam, void *data, bam_hdr_t *hdr)
+static int addBam(const bam1_t *bam, void *data, bam_hdr_t *hdr)
 /* bam_fetch() calls this on each bam alignment retrieved.  Translate each bam
  * into a linkedFeatures item, and add it to tg->items. */
 {
 struct bamTrackData *btd = (struct bamTrackData *)data;
 if (!passesFilters(bam, btd))
     return 0;
 struct linkedFeatures *lf = bamToLf(bam, data);
 if (lf)
     {
     struct track *tg = btd->tg;
     slAddHead(&(tg->items), lf);
+    btd->count++;
+    if (btd->count > btd->maxItems)
+        errAbort(TOOMANYITEMSERROR);
     }
 return 0;
 }
 
 static struct linkedFeatures *lfStub(int startEnd, int orientation)
 /* Make a linkedFeatures for a zero-length item (so that an arrow will be drawn
  * toward the given coord. */
 {
 struct linkedFeatures *lf;
 AllocVar(lf);
 lf->name = cloneString("stub");
 lf->orientation = orientation;
 struct simpleFeature *sf;
 AllocVar(sf);
 sf->start = sf->end = lf->start = lf->end = lf->tallStart = lf->tallEnd = startEnd;
@@ -340,31 +363,31 @@
 /* Make a linkedFeaturesSeries from one or two linkedFeatures elements. */
 {
 struct linkedFeaturesSeries *lfs;
 AllocVar(lfs);
 lfs->name = cloneString(lf->name);
 lfs->grayIx = lf->grayIx;
 if (lf->next != NULL)
     slSort(&lf, linkedFeaturesCmpStart);
 lfs->orientation = 0;
 lfs->start = lf->start;
 lfs->end = lf->next ? max(lf->next->end, lf->end) : lf->end;
 lfs->features = lf;
 return lfs;
 }
 
-int addBamPaired(const bam1_t *bam, void *data, bam_hdr_t *header)
+static int addBamPaired(const bam1_t *bam, void *data, bam_hdr_t *header)
 /* bam_fetch() calls this on each bam alignment retrieved.  Translate each bam
  * into a linkedFeaturesSeries item, and either store it until we find its mate
  * or add it to tg->items. */
 {
 const bam1_core_t *core = &bam->core;
 struct bamTrackData *btd = (struct bamTrackData *)data;
 if (! passesFilters(bam, btd))
     return 0;
 struct linkedFeatures *lf = bamToLf(bam, data);
 struct track *tg = btd->tg;
 if (!(core->flag & BAM_FPAIRED) || (core->flag & BAM_FMUNMAP))
     {
     if (lf->start < winEnd && lf->end > winStart)
 	slAddHead(&(tg->items), lfsFromLf(lf));
     if ((core->flag & BAM_FMUNMAP) && sameString(btd->colorMode, BAM_COLOR_MODE_GRAY) &&
@@ -403,30 +426,33 @@
 	    }
 	else if (sameString(btd->colorMode, BAM_COLOR_MODE_GRAY) &&
 		 sameString(btd->grayMode, BAM_GRAY_MODE_UNPAIRED))
 	    // not properly paired: make it a lighter shade.
 	    lf->grayIx -= 4;
 	hashAdd(btd->pairHash, lf->name, lf);
 	}
     else
 	{
 	lfMate->next = lf;
 	if (min(lfMate->start, lf->start) < winEnd && max(lfMate->end, lf->end) > winStart)
 	    slAddHead(&(tg->items), lfsFromLf(lfMate));
 	hashRemove(btd->pairHash, lf->name);
 	}
     }
+btd->count++;
+if (btd->count > btd->maxItems)
+    errAbort(TOOMANYITEMSERROR);
 return 0;
 }
 
 #define MAX_ITEMS_FOR_MAPBOX 1500
 static void dontMapItem(struct track *tg, struct hvGfx *hvg, void *item,
 			char *itemName, char *mapItemName, int start, int end,
 			int x, int y, int width, int height)
 /* When there are many many items, drawing hgc links can really slow us down. */
 {
 }
 
 static int linkedFeaturesCmpOri(const void *va, const void *vb)
 /* Help sort linkedFeatures by strand, then by starting pos. */
 {
 const struct linkedFeatures *a = *((struct linkedFeatures **)va);
@@ -480,31 +506,31 @@
  * that the data would just end up in dense mode and be super-slow. */
 {
 /* protect against temporary network error */
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     {
     struct hash *pairHash = isPaired ? hashNew(18) : NULL;
     int minAliQual = atoi(cartOrTdbString(cart, tg->tdb, BAM_MIN_ALI_QUAL, BAM_MIN_ALI_QUAL_DEFAULT));
     char *colorMode = cartOrTdbString(cart, tg->tdb, BAM_COLOR_MODE, BAM_COLOR_MODE_DEFAULT);
     char *grayMode = cartOrTdbString(cart, tg->tdb, BAM_GRAY_MODE, BAM_GRAY_MODE_DEFAULT);
     char *userTag = cartOrTdbString(cart, tg->tdb, BAM_COLOR_TAG, BAM_COLOR_TAG_DEFAULT);
     int aliQualShadeMin = 0, aliQualShadeMax = 99, baseQualShadeMin = 0, baseQualShadeMax = 40;
     parseIntRangeSetting(tg->tdb, "aliQualRange", &aliQualShadeMin, &aliQualShadeMax);
     parseIntRangeSetting(tg->tdb, "baseQualRange", &baseQualShadeMin, &baseQualShadeMax);
     struct bamTrackData btd = {tg, pairHash, minAliQual, colorMode, grayMode, userTag,
-			       aliQualShadeMin, aliQualShadeMax, baseQualShadeMin, baseQualShadeMax};
+			       aliQualShadeMin, aliQualShadeMax, baseQualShadeMin, baseQualShadeMax, 0, BAMMaxItems()};
 
     char *fileName = trackDbSetting(tg->tdb, "bigDataUrl");
     if (fileName == NULL)
 	{
 	if (tg->customPt)
 	    {
 	    errAbort("bamLoadItemsCore: can't find bigDataUrl for custom track %s", tg->track);
 	    }
 	else
 	    {
 	    struct sqlConnection *conn = hAllocConnTrack(database, tg->tdb);
 	    fileName = bamFileNameFromTable(conn, tg->table, chromName);
 	    hFreeConn(&conn);
 	    }
 	}
@@ -559,31 +585,32 @@
 		 sameString(grayMode, BAM_GRAY_MODE_ALI_QUAL))
 	    slSort(&(tg->items), linkedFeaturesCmpScore);
 	else
 	    slSort(&(tg->items), linkedFeaturesCmpStart);
 	if (slCount(tg->items) > MAX_ITEMS_FOR_MAPBOX)
 	    {
 	    // flag drawItems to make a mapBox for the whole track
 	    tg->customInt = 1;
 	    tg->mapItem = dontMapItem;
 	    }
 	}
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
-    tg->networkErrMsg = cloneString(errCatch->message->string);
+    tg->items = NULL;
+    tg->networkErrMsg = cloneString("Maximum number of BAM items exceeded. Perhaps zooming in a bit will help?");
     tg->drawItems = bigDrawWarning;
     tg->totalHeight = bigWarnTotalHeight;
     }
 errCatchFree(&errCatch);
 }
 
 
 void bamLoadItems(struct track *tg)
 /* Load single-ended-only BAM data into tg->items item list, unless zoomed out so far
  * that the data would just end up in dense mode and be super-slow. */
 {
 bamLoadItemsCore(tg, FALSE);
 }