Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/Makefile.am
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/Makefile.am	(revision 42991)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/Makefile.am	(revision 42992)
@@ -1,3 +1,13 @@
-SUBDIRS = scripts
+SUBDIRS = scripts web/cgi
 
 CLEANFILES = *~ core core.*
+
+cgi:
+	$(MAKE) -C web/cgi cgi
+
+install-cgi:
+	$(MAKE) -C web/cgi install-cgi
+
+uninstall-cgi:
+	$(MAKE) -C web/cgi uninstall-cgi
+
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/configure.ac
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/configure.ac	(revision 42991)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/configure.ac	(revision 42992)
@@ -2,4 +2,9 @@
 
 AC_INIT([DataStoreServer], [1.0.0], [ipp-support@ifa.hawaii.edu])
+
+# this checks for a working C compiler, but is only needed is
+# we build *.c files, which we do not in DataStoreServer
+# AC_PROG_CC
+
 AC_CONFIG_SRCDIR([scripts])
 
@@ -9,6 +14,22 @@
 AC_PROG_INSTALL
 
+AC_ARG_WITH([cgidir],
+  [AS_HELP_STRING([--with-cgidir=DIR],
+                  [directory for CGI programs])],
+  [cgidir="$withval"],
+  [cgidir=""])
+
+AC_ARG_WITH([dsroot],
+  [AS_HELP_STRING([--with-dsroot=DIR],
+                  [directory for web data files])],
+  [dsroot="$withval"],
+  [dsroot=""])
+
+AC_SUBST([cgidir])
+AC_SUBST([dsroot])
+
 AC_CONFIG_FILES([
   Makefile
+  web/cgi/Makefile
   scripts/Makefile
 ])
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/notes.txt
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/notes.txt	(revision 42991)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/notes.txt	(revision 42992)
@@ -1,2 +1,58 @@
+
+How to create a new installation of the IPP datastore:
+
+* create a database to host the relevant tables:
+  * create a mysql user:
+  > create user 'ipp'@'localhost' identified by 'XXX'
+  > grant all on *.* to 'ipp'@'localhost'
+
+  * in mysql, create the test database:
+  > create database datastore;
+
+  * create the tables:
+  # mysql -h localhost -u ipp -p datastore < scripts/tabledefs.sql
+
+  This generates the tables needed by the datastore web pages and tools
+
+* in DataStoreServer, to install the apache CGI elements:
+  * ./autogen.sh --with-cgidir=/MY/DATASTORE/ds-cgi --with-dsroot=/MY/DATASTORE/dsroot
+  * make cgi
+  * then edit /MY/DATASTORE/ds-cgi/dsshellconfig
+    ** supply the perl path of the user built to support the datastore
+    ** supply the database server information
+    ** supply the dsroot location
+
+* install the apache vhost configuration file:
+  * NOTE: this likely needs root access
+  * cp DataStoreServer/web/conf/ubuntu-datastore.conf /etc/apache2/sites-available
+  * edit to match location of datastore/dsroot and ds-cgi
+  * enable the new site (a2ensite)
+  * enable required modules: rewrite, cgi, includes
+  * start or restart the apache server
+
+==============================================================
+
+* datastore apache elements consist of CGI scripts and the directory hierarchy for data products
+
+* Things to be installed in ds-cgi:
+  * dsindex.cgi
+  * dstxtindex.cgi
+  * dsgetindex
+  * dsfsindex
+  * dsprodindex
+  * dsrootindex
+  * dsshellconfig -- NEEDS to be modified to define the configuration
+
+* Things needed in the user build by the apache server:
+  * dsdbh.pm in PERL
+  * DBD/mysql.pm
+
+* Things installed as user tools:
+  * dsreg
+  * dsprodtool
+
+*
+
+----------
 
 Here is the current (2026.04.11) directory listing of /export/ipp113.0/datastore/ds-cgi:
@@ -48,14 +104,2 @@
 ---------
 
-How to set up a local database to host the IPP datastore:
-
-* create a database to host the relevant tables:
-  in mysql, create the test database:
-  > create database test_datastore;
-  create the tables:
-  # mysql -h localhost -u ipp -p test_datastore < scripts/tabledefs.sql
-
-  This generates the tables needed by the datastore web pages and tools
-
-----------
-
Index: anches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsfsindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsfsindex	(revision 42991)
+++ 	(revision )
@@ -1,117 +1,0 @@
-#!/usr/bin/env perl
-
-# list the filesets in a data store product
-# The only ipp specific code is the function that gets the database handle and the
-# exit status codes
-
-use strict;
-use warnings;
-
-use dsdbh;
-use DBI;
-
-my $PS_EXIT_CONFIG_ERROR = 3;
-my $PS_EXIT_DATA_ERROR = 5;
-
-my $fileset = pop;
-die("must specify fileset to list") unless defined $fileset;
-
-my $product = pop;
-die("must specify product to list") unless defined $product;
-
-my $dbh = getDBHandle();
-
-# Look up the product id
-my $prod_stmt = $dbh->prepare("SELECT prod_id FROM dsProduct WHERE prod_name = '$product'");
-$prod_stmt->execute();
-my $prod = $prod_stmt->fetchrow_hashref();
-if (! defined $prod) {
-    print STDERR "failed to find $product in product list\n";
-    exit ($PS_EXIT_DATA_ERROR);
-}
-
-my $prod_id = $prod->{prod_id};
-
-my $stmt = $dbh->prepare("SELECT fileset_id FROM dsFileset WHERE fileset_name = \'$fileset\'" .
-                    " AND prod_id = $prod_id");
-$stmt->execute();
-my $fs = $stmt->fetchrow_hashref();
-if (!defined $fs) {
-    print STDERR "failed to find $fileset in $product\n";
-    exit ($PS_EXIT_DATA_ERROR);
-}
-
-my $fs_id = $fs->{fileset_id};
-
-
-# get the headers for all of the type specific columns
-# a fileset may contain files with multiple types. We print out a new
-# header each time the type in the list changes.
-# save the header
-
-$stmt = $dbh->prepare("SELECT * FROM dsFileType");
-$stmt->execute();
-my %fileTypes;
-while( my @row = $stmt->fetchrow_array()) {
-    my $type = $row[0];
-    $fileTypes{$type} = \@row;
-}
-
-my $header = "# fileID          |bytes   |md5sum                          |type        |";
-
-$stmt = $dbh->prepare("SELECT * from dsFile WHERE fileset_id = $fs_id ORDER BY file_id");
-$stmt->execute();
-
-my $print_header=1;
-my $last_type = "";
-my $last_header = "";
-while( my $row = $stmt->fetchrow_hashref()) {
-    my $type = $row->{type};
-
-    # if the type changes print out a new header
-    if ($type ne $last_type) {
-        $print_header = 1;
-        $last_type = $type;
-    }
-    if ($print_header) {
-        # add the type specific columns
-        my $typeheader = "";
-        my $fileTypeDef = $fileTypes{$type};
-        for (my $i= 1; $i <=4; $i++) {
-            if (!defined $fileTypeDef->[$i]) {
-                last;
-            }
-            $typeheader .= sprintf "%-8s|", $fileTypeDef->[$i];
-        }
-        my $newheader = "$header$typeheader";
-        # if the header actually changed (due to type specific columns) print it out
-        if ($newheader ne $last_header) {
-            print "$newheader\n";
-            $last_header = $newheader;
-        }
-        $print_header = 0;
-    }
-
-    my $line = sprintf "%-18s|%-8s|%-32s|%-12s|", 
-        $row->{file_name}, $row->{bytes}, $row->{md5sum}, $row->{type};
-
-    # now add any type specific columns that are defined (8 character minimum width is arbitrary)
-    if (defined $row->{type_col_0}) {
-        $line .= sprintf "%-8s|", $row->{type_col_0};
-        if (defined $row->{type_col_1}) {
-            $line .= sprintf "%-8s|", $row->{type_col_1};
-            if (defined $row->{type_col_2}) {
-                $line .= sprintf "%-8s|", $row->{type_col_2};
-                if (defined $row->{type_col_3}) {
-                    $line .= sprintf "%-8s|", $row->{type_col_3};
-                }
-            }
-        }
-    }
-
-    print "$line\n";
-}
-if ($print_header) {
-    # empty file set
-    print "$header\n";
-}
Index: anches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsprodindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsprodindex	(revision 42991)
+++ 	(revision )
@@ -1,146 +1,0 @@
-#!/usr/bin/env perl
-
-# list the filesets in a data store product
-
-use strict;
-use warnings;
-
-use DBI;
-use dsdbh;
-
-my $PS_EXIT_CONFIG_ERROR = 3;
-my $PS_EXIT_DATA_ERROR = 5;
-my $product = shift;
-die("must specify product to list") unless defined $product;
-
-# optional fileset name. Ignore filesets up to and including $after_fileset
-my $after_fileset = shift;
-
-my $dbh = getDBHandle();
-
-my $prod_stmt = $dbh->prepare("SELECT * FROM dsProduct WHERE prod_name = '$product'");
-$prod_stmt->execute();
-my $prod = $prod_stmt->fetchrow_hashref();
-if (! defined $prod) {
-    print STDERR "failed to find $product in product list\n";
-    exit ($PS_EXIT_DATA_ERROR);
-}
-
-# first get the column names
-
-# we have at least 3 columns
-my @header_col = ("# filesetID", "time registered", "type");
-my $numCols = 3;
-
-# now add any product specific columns that are defined. Note if there is no header value
-# then any values in the rows will be ignored
-my $last_prod_col = -1;
-for (my $i = 0; $i < 8; $i++) {
-    my $col_key = "prod_col_$i";
-    my $col_name = $prod->{$col_key};
-
-    last if ! defined $col_name;
-
-    $header_col[$numCols++] = $col_name;
-    $last_prod_col = $i;
-}
-
-# now set up arrays for the values for each column. 
-my @column;
-for (my $i = 0; $i < $numCols; $i++) {
-
-    my @col = ($header_col[$i]);
-
-    $column[$i] = \@col;
-}
-    
-
-my $prod_id = $prod->{prod_id};
-
-my $stmt;
-my $after_clause = "";
-if ($after_fileset) {
-    # client wants only filesets registered after $after_fileset
-    # Since fileset_id is auto increment the desired filesets will have a larger fileset_id than
-    # the target. Look up it's fileset_id
-
-    $stmt = $dbh->prepare("SELECT fileset_id FROM dsFileset " .
-                            "WHERE prod_id = $prod_id AND fileset_name = \'$after_fileset\'");
-    $stmt->execute();
-    my $fs = $stmt->fetchrow_hashref();
-    if ($fs) {
-        $after_clause = " AND fileset_id > $fs->{fileset_id}";
-    } else {
-        # $after_fileset not found
-        #
-        # XXX: the spec doesn't say what should happen in this case.
-        # We used to follow the conductor implementation and return the whole list.
-        # but that is definitely not the right thing to do in our context.
-        # Exit with an error
-        exit 404;
-    }
-}
-        
-$stmt = $dbh->prepare("SELECT * FROM dsFileset WHERE prod_id = $prod_id AND hide = 0  $after_clause ORDER BY fileset_id");
-$stmt->execute();
-
-# we at least have the header row to output
-my $numRows = 1;
-while( my $row = $stmt->fetchrow_hashref()) {
-
-    $numRows++;
-    my $daytime = $row->{reg_time};
-    (my $date, my $time) = split " ", $daytime;
-
-    my $reg_time = $date . "T" . $time . "Z";
-
-    my $line = sprintf "%-11s|%-20s|%-9s|", $row->{fileset_name}, $reg_time, $row->{type};
-
-    # this probably would have been simpler had I used fetchrow array
-    push @{$column[0]}, $row->{fileset_name};
-    push @{$column[1]}, $reg_time;
-    push @{$column[2]}, $row->{type};
-    
-    for (my $i = 0; $i <= $last_prod_col; $i++) {
-        my $col_key = "prod_col_$i";
-        my $col_val = $row->{$col_key};
-
-        if (!defined($col_val)) {
-            $col_val = "null";
-        }
-        push @{$column[$i+3]}, $col_val;
-    }
-}
-
-# find the largest value width in each column
-my @col_widths;
-for (my $c = 0; $c < $numCols; $c++) {
-    $col_widths[$c] = 0;
-    my $col = $column[$c];
-    for (my $i = 0; $i < $numRows; $i++) {
-        my $val = $col->[$i];
-        if (!defined($val)) {
-            die "value for column $c in row $i is null";
-            next;
-        }
-        my $len = length($val);
-        if ($len > $col_widths[$c]) {
-            $col_widths[$c] = $len;
-        }
-    }
-}
-
-# print out the results
-for (my $r = 0; $r < $numRows; $r++) {
-    for (my $c = 0; $c < $numCols; $c++) {
-        my $val = $column[$c]->[$r];
-        my $width = $col_widths[$c];
-        if (defined($val)) {
-            printf "%-*s|", $width, $val;
-        } else {
-            die "value for column $c in row $r is null";
-        }
-    }
-    print "\n";
-}
-    
Index: anches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsrootindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/scripts/dsrootindex	(revision 42991)
+++ 	(revision )
@@ -1,57 +1,0 @@
-#!/usr/bin/env perl
-#
-# dsrootindex:  Extract the root data store index from the database.
-# The output is in DataStore index.txt format
-#
-
-use strict;
-use warnings;
-
-use dsdbh;
-
-use DBI;
-
-my $PS_EXIT_CONFIG_ERROR = 3;
-
-# set this variable to redirect listings of the root datastore directory
-# to the password protected php page on the postage stamp server web site.
-# Since the data store is restricted by IP address now this is no longer necessary
-my $redirect_root_to_pstamp = 0;
-if ($redirect_root_to_pstamp) {
-
-        print '
-        <html>
-        <head>
-        <meta HTTP-EQUIV="REFRESH" content="0; url=http://pstamp.ipp.ifa.hawaii.edu/dsroot.php">
-        </head>
-        </html>
-        ';
-
-        exit 0;
-}
-
-my $dbh = getDBHandle();
-
-my $stmt = $dbh->prepare("SELECT * FROM dsProduct ORDER BY type");
-$stmt->execute();
-
-print "# productID  |Most recent |Time registered     |type     |Description                     |\n";
-
-while( my $row = $stmt->fetchrow_hashref()) {
-    my $prod_id = $row->{prod_id};
-
-    my ($date, $time) = split / /, $row->{last_update};
-    my $datetime = $date . "T" . $time . "Z";
-
-    my $last_fs =  $row->{last_fs};
-    if (!$last_fs) {
-        # dsrootls is fussy when this field is blank
-        $last_fs = "none";
-    }
-
-    my $line = sprintf "%-13s|%-12s|%-20s|%-9s|%-32s|\n",  $row->{prod_name}, $last_fs, $datetime,
-        $row->{type}, $row->{description};
-
-    # XXX EAM : security by obfuscation
-    print $line;
-}
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/Makefile.am
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/Makefile.am	(revision 42992)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/Makefile.am	(revision 42992)
@@ -0,0 +1,45 @@
+cgi_scripts = \
+  dsindex.cgi \
+  dstxtindex.cgi \
+  dsgetindex \
+  dsfsindex \
+  dsprodindex \
+  dsrootindex \
+  dsshellconfig
+
+dsroot_files = index.txt.template index.txt
+
+EXTRA_DIST = $(cgi_scripts) $(dsroot_files)
+
+install-cgi:
+	@if test -z "$(cgidir)"; then \
+	  echo "Error: cgidir is not set. Use --with-cgidir=DIR"; \
+	  exit 1; \
+	fi
+	@if test -z "$(dsroot)"; then \
+	  echo "Error: dsroot is not set. Use --with-dsroot=DIR"; \
+	  exit 1; \
+	fi
+	$(MKDIR_P) $(DESTDIR)$(cgidir)
+	@for f in $(cgi_scripts); do \
+	  $(INSTALL_SCRIPT) $$f $(DESTDIR)$(cgidir)/$$f; \
+	done
+	$(MKDIR_P) $(DESTDIR)$(dsroot)
+	@for f in $(dsroot_files); do \
+	  $(INSTALL_DATA) $$f $(DESTDIR)$(dsroot)/$$f; \
+	done
+	@echo ""
+	@echo "datastore apache CGI code installation complete."
+	@echo "Now please edit $(DESTDIR)$(cgidir)/dsshellconfig"
+	@echo "and add the required settings."
+	@echo ""
+
+uninstall-cgi:
+	@for f in $(cgi_scripts); do \
+	  rm -f $(DESTDIR)$(cgidir)/$$f; \
+	done
+	@for f in $(dsroot_files); do \
+	  rm -f $(DESTDIR)$(dsroot)/$$f; \
+	done
+
+cgi: install-cgi
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsfsindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsfsindex	(revision 42992)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsfsindex	(revision 42992)
@@ -0,0 +1,117 @@
+#!/usr/bin/env perl
+
+# list the filesets in a data store product
+# The only ipp specific code is the function that gets the database handle and the
+# exit status codes
+
+use strict;
+use warnings;
+
+use dsdbh;
+use DBI;
+
+my $PS_EXIT_CONFIG_ERROR = 3;
+my $PS_EXIT_DATA_ERROR = 5;
+
+my $fileset = pop;
+die("must specify fileset to list") unless defined $fileset;
+
+my $product = pop;
+die("must specify product to list") unless defined $product;
+
+my $dbh = getDBHandle();
+
+# Look up the product id
+my $prod_stmt = $dbh->prepare("SELECT prod_id FROM dsProduct WHERE prod_name = '$product'");
+$prod_stmt->execute();
+my $prod = $prod_stmt->fetchrow_hashref();
+if (! defined $prod) {
+    print STDERR "failed to find $product in product list\n";
+    exit ($PS_EXIT_DATA_ERROR);
+}
+
+my $prod_id = $prod->{prod_id};
+
+my $stmt = $dbh->prepare("SELECT fileset_id FROM dsFileset WHERE fileset_name = \'$fileset\'" .
+                    " AND prod_id = $prod_id");
+$stmt->execute();
+my $fs = $stmt->fetchrow_hashref();
+if (!defined $fs) {
+    print STDERR "failed to find $fileset in $product\n";
+    exit ($PS_EXIT_DATA_ERROR);
+}
+
+my $fs_id = $fs->{fileset_id};
+
+
+# get the headers for all of the type specific columns
+# a fileset may contain files with multiple types. We print out a new
+# header each time the type in the list changes.
+# save the header
+
+$stmt = $dbh->prepare("SELECT * FROM dsFileType");
+$stmt->execute();
+my %fileTypes;
+while( my @row = $stmt->fetchrow_array()) {
+    my $type = $row[0];
+    $fileTypes{$type} = \@row;
+}
+
+my $header = "# fileID          |bytes   |md5sum                          |type        |";
+
+$stmt = $dbh->prepare("SELECT * from dsFile WHERE fileset_id = $fs_id ORDER BY file_id");
+$stmt->execute();
+
+my $print_header=1;
+my $last_type = "";
+my $last_header = "";
+while( my $row = $stmt->fetchrow_hashref()) {
+    my $type = $row->{type};
+
+    # if the type changes print out a new header
+    if ($type ne $last_type) {
+        $print_header = 1;
+        $last_type = $type;
+    }
+    if ($print_header) {
+        # add the type specific columns
+        my $typeheader = "";
+        my $fileTypeDef = $fileTypes{$type};
+        for (my $i= 1; $i <=4; $i++) {
+            if (!defined $fileTypeDef->[$i]) {
+                last;
+            }
+            $typeheader .= sprintf "%-8s|", $fileTypeDef->[$i];
+        }
+        my $newheader = "$header$typeheader";
+        # if the header actually changed (due to type specific columns) print it out
+        if ($newheader ne $last_header) {
+            print "$newheader\n";
+            $last_header = $newheader;
+        }
+        $print_header = 0;
+    }
+
+    my $line = sprintf "%-18s|%-8s|%-32s|%-12s|", 
+        $row->{file_name}, $row->{bytes}, $row->{md5sum}, $row->{type};
+
+    # now add any type specific columns that are defined (8 character minimum width is arbitrary)
+    if (defined $row->{type_col_0}) {
+        $line .= sprintf "%-8s|", $row->{type_col_0};
+        if (defined $row->{type_col_1}) {
+            $line .= sprintf "%-8s|", $row->{type_col_1};
+            if (defined $row->{type_col_2}) {
+                $line .= sprintf "%-8s|", $row->{type_col_2};
+                if (defined $row->{type_col_3}) {
+                    $line .= sprintf "%-8s|", $row->{type_col_3};
+                }
+            }
+        }
+    }
+
+    print "$line\n";
+}
+if ($print_header) {
+    # empty file set
+    print "$header\n";
+}
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsprodindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsprodindex	(revision 42992)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsprodindex	(revision 42992)
@@ -0,0 +1,146 @@
+#!/usr/bin/env perl
+
+# list the filesets in a data store product
+
+use strict;
+use warnings;
+
+use DBI;
+use dsdbh;
+
+my $PS_EXIT_CONFIG_ERROR = 3;
+my $PS_EXIT_DATA_ERROR = 5;
+my $product = shift;
+die("must specify product to list") unless defined $product;
+
+# optional fileset name. Ignore filesets up to and including $after_fileset
+my $after_fileset = shift;
+
+my $dbh = getDBHandle();
+
+my $prod_stmt = $dbh->prepare("SELECT * FROM dsProduct WHERE prod_name = '$product'");
+$prod_stmt->execute();
+my $prod = $prod_stmt->fetchrow_hashref();
+if (! defined $prod) {
+    print STDERR "failed to find $product in product list\n";
+    exit ($PS_EXIT_DATA_ERROR);
+}
+
+# first get the column names
+
+# we have at least 3 columns
+my @header_col = ("# filesetID", "time registered", "type");
+my $numCols = 3;
+
+# now add any product specific columns that are defined. Note if there is no header value
+# then any values in the rows will be ignored
+my $last_prod_col = -1;
+for (my $i = 0; $i < 8; $i++) {
+    my $col_key = "prod_col_$i";
+    my $col_name = $prod->{$col_key};
+
+    last if ! defined $col_name;
+
+    $header_col[$numCols++] = $col_name;
+    $last_prod_col = $i;
+}
+
+# now set up arrays for the values for each column. 
+my @column;
+for (my $i = 0; $i < $numCols; $i++) {
+
+    my @col = ($header_col[$i]);
+
+    $column[$i] = \@col;
+}
+    
+
+my $prod_id = $prod->{prod_id};
+
+my $stmt;
+my $after_clause = "";
+if ($after_fileset) {
+    # client wants only filesets registered after $after_fileset
+    # Since fileset_id is auto increment the desired filesets will have a larger fileset_id than
+    # the target. Look up it's fileset_id
+
+    $stmt = $dbh->prepare("SELECT fileset_id FROM dsFileset " .
+                            "WHERE prod_id = $prod_id AND fileset_name = \'$after_fileset\'");
+    $stmt->execute();
+    my $fs = $stmt->fetchrow_hashref();
+    if ($fs) {
+        $after_clause = " AND fileset_id > $fs->{fileset_id}";
+    } else {
+        # $after_fileset not found
+        #
+        # XXX: the spec doesn't say what should happen in this case.
+        # We used to follow the conductor implementation and return the whole list.
+        # but that is definitely not the right thing to do in our context.
+        # Exit with an error
+        exit 404;
+    }
+}
+        
+$stmt = $dbh->prepare("SELECT * FROM dsFileset WHERE prod_id = $prod_id AND hide = 0  $after_clause ORDER BY fileset_id");
+$stmt->execute();
+
+# we at least have the header row to output
+my $numRows = 1;
+while( my $row = $stmt->fetchrow_hashref()) {
+
+    $numRows++;
+    my $daytime = $row->{reg_time};
+    (my $date, my $time) = split " ", $daytime;
+
+    my $reg_time = $date . "T" . $time . "Z";
+
+    my $line = sprintf "%-11s|%-20s|%-9s|", $row->{fileset_name}, $reg_time, $row->{type};
+
+    # this probably would have been simpler had I used fetchrow array
+    push @{$column[0]}, $row->{fileset_name};
+    push @{$column[1]}, $reg_time;
+    push @{$column[2]}, $row->{type};
+    
+    for (my $i = 0; $i <= $last_prod_col; $i++) {
+        my $col_key = "prod_col_$i";
+        my $col_val = $row->{$col_key};
+
+        if (!defined($col_val)) {
+            $col_val = "null";
+        }
+        push @{$column[$i+3]}, $col_val;
+    }
+}
+
+# find the largest value width in each column
+my @col_widths;
+for (my $c = 0; $c < $numCols; $c++) {
+    $col_widths[$c] = 0;
+    my $col = $column[$c];
+    for (my $i = 0; $i < $numRows; $i++) {
+        my $val = $col->[$i];
+        if (!defined($val)) {
+            die "value for column $c in row $i is null";
+            next;
+        }
+        my $len = length($val);
+        if ($len > $col_widths[$c]) {
+            $col_widths[$c] = $len;
+        }
+    }
+}
+
+# print out the results
+for (my $r = 0; $r < $numRows; $r++) {
+    for (my $c = 0; $c < $numCols; $c++) {
+        my $val = $column[$c]->[$r];
+        my $width = $col_widths[$c];
+        if (defined($val)) {
+            printf "%-*s|", $width, $val;
+        } else {
+            die "value for column $c in row $r is null";
+        }
+    }
+    print "\n";
+}
+    
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsrootindex
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsrootindex	(revision 42992)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/dsrootindex	(revision 42992)
@@ -0,0 +1,57 @@
+#!/usr/bin/env perl
+#
+# dsrootindex:  Extract the root data store index from the database.
+# The output is in DataStore index.txt format
+#
+
+use strict;
+use warnings;
+
+use dsdbh;
+
+use DBI;
+
+my $PS_EXIT_CONFIG_ERROR = 3;
+
+# set this variable to redirect listings of the root datastore directory
+# to the password protected php page on the postage stamp server web site.
+# Since the data store is restricted by IP address now this is no longer necessary
+my $redirect_root_to_pstamp = 0;
+if ($redirect_root_to_pstamp) {
+
+        print '
+        <html>
+        <head>
+        <meta HTTP-EQUIV="REFRESH" content="0; url=http://pstamp.ipp.ifa.hawaii.edu/dsroot.php">
+        </head>
+        </html>
+        ';
+
+        exit 0;
+}
+
+my $dbh = getDBHandle();
+
+my $stmt = $dbh->prepare("SELECT * FROM dsProduct ORDER BY type");
+$stmt->execute();
+
+print "# productID  |Most recent |Time registered     |type     |Description                     |\n";
+
+while( my $row = $stmt->fetchrow_hashref()) {
+    my $prod_id = $row->{prod_id};
+
+    my ($date, $time) = split / /, $row->{last_update};
+    my $datetime = $date . "T" . $time . "Z";
+
+    my $last_fs =  $row->{last_fs};
+    if (!$last_fs) {
+        # dsrootls is fussy when this field is blank
+        $last_fs = "none";
+    }
+
+    my $line = sprintf "%-13s|%-12s|%-20s|%-9s|%-32s|\n",  $row->{prod_name}, $last_fs, $datetime,
+        $row->{type}, $row->{description};
+
+    # XXX EAM : security by obfuscation
+    print $line;
+}
Index: /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/index.txt
===================================================================
--- /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/index.txt	(revision 42992)
+++ /branches/eam_branches/ipp-pstamp-20260421/DataStoreServer/web/cgi/index.txt	(revision 42992)
@@ -0,0 +1,1 @@
+<!--#include virtual="/ds-cgi/dstxtindex.cgi" -->
