summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
authorcperciva <cperciva@FreeBSD.org>2005-08-08 20:10:06 +0000
committercperciva <cperciva@FreeBSD.org>2005-08-08 20:10:06 +0000
commit7d8af51fdcf9b077da486bdb2f6d70ba885d1aab (patch)
tree957b70742d3c4cf9d900360e9c50e543d96402a8 /usr.sbin
parent65c3a00587493225fc55992441c13a31c2f09232 (diff)
downloadFreeBSD-src-7d8af51fdcf9b077da486bdb2f6d70ba885d1aab.zip
FreeBSD-src-7d8af51fdcf9b077da486bdb2f6d70ba885d1aab.tar.gz
Add portsnap to the base system. This is a secure, easy to use,
fast, lightweight, and generally good way for users to keep their ports trees up to date. This is version 0.9.4 from the ports tree (sysutils/portsnap) with the following changes: 1. The experimental pipelined http code is enabled. No seatbelts in -CURRENT. (^_^) 2. The working directory has moved from /usr/local/portsnap to /var/db/portsnap (as discussed on -arch two days ago). 3. Portsnap now fetches a list of mirrors (distributed as DNS SRV records) and selects one randomly. This should help to avoid the uneven loading which plagues the cvsup mirror network. 4. The license is now 2-clause BSD instead of 3-clause BSD. 5. Various incidental changes to make portsnap fit into the base system's build mechanics. X-MFC-After: 6.0-RELEASE X-MFC-Before: 5.5-RELEASE X-MFC-To: RELENG_6, RELENG_5, ports discussed on: -arch and several other places "yes please" from: simon, remko, flz, Diane Bruce thinks this is a great idea: bsdimp Hopes he didn't forget any files: cperciva
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/Makefile1
-rw-r--r--usr.sbin/portsnap/Makefile5
-rw-r--r--usr.sbin/portsnap/Makefile.inc5
-rw-r--r--usr.sbin/portsnap/make_index/Makefile9
-rw-r--r--usr.sbin/portsnap/make_index/make_index.c491
-rw-r--r--usr.sbin/portsnap/phttpget/Makefile9
-rw-r--r--usr.sbin/portsnap/phttpget/phttpget.c598
-rw-r--r--usr.sbin/portsnap/portsnap/Makefile6
-rw-r--r--usr.sbin/portsnap/portsnap/portsnap.8208
-rw-r--r--usr.sbin/portsnap/portsnap/portsnap.sh901
10 files changed, 2233 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 0737266..46173c3 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -111,6 +111,7 @@ SUBDIR= ac \
pmccontrol \
pmcstat \
${_pnpinfo} \
+ portsnap \
powerd \
ppp \
${_pppctl} \
diff --git a/usr.sbin/portsnap/Makefile b/usr.sbin/portsnap/Makefile
new file mode 100644
index 0000000..0f77bcb
--- /dev/null
+++ b/usr.sbin/portsnap/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= portsnap make_index phttpget
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/portsnap/Makefile.inc b/usr.sbin/portsnap/Makefile.inc
new file mode 100644
index 0000000..5f6527e
--- /dev/null
+++ b/usr.sbin/portsnap/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+LIBEXECDIR?= /usr/libexec
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/portsnap/make_index/Makefile b/usr.sbin/portsnap/make_index/Makefile
new file mode 100644
index 0000000..65b1a8e
--- /dev/null
+++ b/usr.sbin/portsnap/make_index/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= make_index
+NO_MAN=
+WARNS?= 6
+
+BINDIR= ${LIBEXECDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/make_index/make_index.c b/usr.sbin/portsnap/make_index/make_index.c
new file mode 100644
index 0000000..6139fad
--- /dev/null
+++ b/usr.sbin/portsnap/make_index/make_index.c
@@ -0,0 +1,491 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct port;
+
+typedef union {
+ char * name;
+ struct port * p;
+} DEP;
+
+typedef struct port {
+ char * pkgname;
+ char * portdir;
+ char * prefix;
+ char * comment;
+ char * pkgdescr;
+ char * maintainer;
+ char * categories;
+ size_t n_edep;
+ DEP * edep;
+ size_t n_pdep;
+ DEP * pdep;
+ size_t n_fdep;
+ DEP * fdep;
+ size_t n_bdep;
+ DEP * bdep;
+ size_t n_rdep;
+ DEP * rdep;
+ char * www;
+ int recursed;
+} PORT;
+
+static void usage(void);
+static char * strdup2(const char *str);
+static DEP * makelist(char * str, size_t * n);
+static PORT * portify(char * line);
+static int portcompare(char * a, char * b);
+static void heapifyports(PORT **pp, size_t size, size_t pos);
+static PORT * findport(PORT ** pp, size_t st, size_t en, char * name);
+static void translateport(PORT ** pp, size_t pplen, PORT * p);
+static DEP * recurse_one(DEP * d, size_t * nd);
+static void recurse(PORT * p);
+static void heapifypkgs(DEP * d, size_t size, size_t pos);
+static void sortpkgs(DEP * d, size_t nd);
+static void printport(PORT * p);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: make_index file\n");
+ exit(1);
+ /* NOTREACHED */
+}
+
+static char *
+strdup2(const char *str)
+{
+ char * r;
+
+ r = strdup(str);
+ if (r == NULL)
+ err(1, "strdup");
+ return r;
+}
+
+/* Take a space-separated list and return an array of (char *) */
+static DEP *
+makelist(char * str, size_t * n)
+{
+ DEP * d;
+ size_t i;
+
+ /* No depends at all? */
+ if (str[0] == 0) {
+ *n = 0;
+ return NULL;
+ }
+
+ /* Count the number of fields */
+ *n = 1;
+ for (i = 0; str[i] != 0; i++)
+ if (str[i] == ' ')
+ (*n)++;
+
+ /* Allocate and fill an array */
+ d = malloc(*n * sizeof(DEP));
+ for (i = 0; i < *n; i++) {
+ d[i].name = strdup2(strsep(&str, " "));
+
+ /* Strip trailing slashes */
+ if (d[i].name[strlen(d[i].name) - 1] == '/')
+ d[i].name[strlen(d[i].name) - 1] = 0;
+ }
+
+ return d;
+}
+
+/* Take a port's describe line and split it into fields */
+static PORT *
+portify(char * line)
+{
+ PORT * p;
+ size_t i, n;
+
+ /* Verify that line has the right number of fields */
+ for (n = i = 0; line[i] != 0; i++)
+ if (line[i] == '|')
+ n++;
+ if (n != 12)
+ errx(1, "Port describe line is corrupt:\n%s\n", line);
+
+ p = malloc(sizeof(PORT));
+ if (p == NULL)
+ err(1, "malloc(PORT)");
+
+ p->pkgname = strdup2(strsep(&line, "|"));
+ p->portdir = strdup2(strsep(&line, "|"));
+ p->prefix = strdup2(strsep(&line, "|"));
+ p->comment = strdup2(strsep(&line, "|"));
+ p->pkgdescr = strdup2(strsep(&line, "|"));
+ p->maintainer = strdup2(strsep(&line, "|"));
+ p->categories = strdup2(strsep(&line, "|"));
+ p->edep = makelist(strsep(&line, "|"), &p->n_edep);
+ p->pdep = makelist(strsep(&line, "|"), &p->n_pdep);
+ p->fdep = makelist(strsep(&line, "|"), &p->n_fdep);
+ p->bdep = makelist(strsep(&line, "|"), &p->n_bdep);
+ p->rdep = makelist(strsep(&line, "|"), &p->n_rdep);
+ p->www = strdup2(strsep(&line, "|"));
+
+ p->recursed = 0;
+
+ /*
+ * line will now be equal to NULL -- we counted the field
+ * separators at the top of the function.
+ */
+
+ return p;
+}
+
+/* Returns -1, 0, or 1 based on a comparison of the portdir strings */
+static int
+portcompare(char * a, char * b)
+{
+ size_t i;
+
+ /* Find first non-matching position */
+ for (i = 0; ; i++) {
+ if (a[i] != b[i])
+ break;
+ if (a[i] == 0) /* End of strings */
+ return 0;
+ }
+
+ /* One string is a prefix of the other */
+ if (a[i] == 0)
+ return -1;
+ if (b[i] == 0)
+ return 1;
+
+ /* One string has a category which is a prefix of the other */
+ if (a[i] == '/')
+ return -1;
+ if (b[i] == '/')
+ return 1;
+
+ /* The two strings are simply different */
+ if (a[i] < b[i])
+ return -1;
+ else
+ return 1;
+}
+
+/* Heapify (PORT *) number pos in a pseudo-heap pp[0]..pp[size - 1] */
+static void
+heapifyports(PORT **pp, size_t size, size_t pos)
+{
+ size_t i = pos;
+ PORT * tmp;
+
+top:
+ /* Find the largest value out of {pos, 2*pos+1, 2*pos+2} */
+ if ((2 * pos + 1 < size) &&
+ (portcompare(pp[i]->portdir, pp[2 * pos + 1]->portdir) < 0))
+ i = 2 * pos + 1;
+ if ((2 * pos + 2 < size) &&
+ (portcompare(pp[i]->portdir, pp[2 * pos + 2]->portdir) < 0))
+ i = 2 * pos + 2;
+
+ /* If necessary, swap elements and iterate down the tree. */
+ if (i != pos) {
+ tmp = pp[pos];
+ pp[pos] = pp[i];
+ pp[i] = tmp;
+ pos = i;
+ goto top;
+ }
+}
+
+/* Translate a port directory name into a (PORT *), and free the name */
+static PORT *
+findport(PORT ** pp, size_t st, size_t en, char * name)
+{
+ size_t mid;
+ int r;
+
+ if (st == en)
+ errx(1, "Unresolved dependency: %s", name);
+
+ mid = (st + en) / 2;
+ r = portcompare(pp[mid]->portdir, name);
+
+ if (r == 0) {
+ free(name);
+ return pp[mid];
+ } else if (r < 0)
+ return findport(pp, mid + 1, en, name);
+ else
+ return findport(pp, st, mid, name);
+}
+
+/* Translate all depends from names into PORT *s */
+static void
+translateport(PORT ** pp, size_t pplen, PORT * p)
+{
+ size_t i;
+
+ for (i = 0; i < p->n_edep; i++)
+ p->edep[i].p = findport(pp, 0, pplen, p->edep[i].name);
+ for (i = 0; i < p->n_pdep; i++)
+ p->pdep[i].p = findport(pp, 0, pplen, p->pdep[i].name);
+ for (i = 0; i < p->n_fdep; i++)
+ p->fdep[i].p = findport(pp, 0, pplen, p->fdep[i].name);
+ for (i = 0; i < p->n_bdep; i++)
+ p->bdep[i].p = findport(pp, 0, pplen, p->bdep[i].name);
+ for (i = 0; i < p->n_rdep; i++)
+ p->rdep[i].p = findport(pp, 0, pplen, p->rdep[i].name);
+}
+
+/* Recurse on one specific depends list */
+static DEP *
+recurse_one(DEP * d, size_t * nd)
+{
+ size_t i, j, k, n, N;
+
+ N = n = *nd;
+ for (i = 0; i < n; i++) {
+ recurse(d[i].p);
+ for (j = 0; j < d[i].p->n_rdep; j++) {
+ for (k = 0; k < N; k++) {
+ if (d[i].p->rdep[j].p == d[k].p)
+ break;
+ }
+ if (k == N) {
+ N++;
+ if (N >= *nd) {
+ *nd += *nd;
+ d = realloc(d, *nd * sizeof(DEP));
+ if (d == NULL)
+ err(1, "realloc(d)");
+ }
+ d[k].p = d[i].p->rdep[j].p;
+ }
+ }
+ }
+ *nd = N;
+
+ return d;
+}
+
+/* Recurse on the depends lists */
+static void
+recurse(PORT * p)
+{
+ if (p->recursed != 0)
+ return;
+ p->recursed = 1;
+
+ p->edep = recurse_one(p->edep, &p->n_edep);
+ p->pdep = recurse_one(p->pdep, &p->n_pdep);
+ p->fdep = recurse_one(p->fdep, &p->n_fdep);
+ p->bdep = recurse_one(p->bdep, &p->n_bdep);
+ p->rdep = recurse_one(p->rdep, &p->n_rdep);
+}
+
+/* Heapify an element in a package list */
+static void
+heapifypkgs(DEP * d, size_t size, size_t pos)
+{
+ size_t i = pos;
+ PORT * tmp;
+
+top:
+ /* Find the largest value out of {pos, 2*pos+1, 2*pos+2} */
+ if ((2 * pos + 1 < size) &&
+ (strcmp(d[i].p->pkgname, d[2 * pos + 1].p->pkgname) < 0))
+ i = 2 * pos + 1;
+ if ((2 * pos + 2 < size) &&
+ (strcmp(d[i].p->pkgname, d[2 * pos + 2].p->pkgname) < 0))
+ i = 2 * pos + 2;
+
+ /* If necessary, swap elements and iterate down the tree. */
+ if (i != pos) {
+ tmp = d[pos].p;
+ d[pos].p = d[i].p;
+ d[i].p = tmp;
+ pos = i;
+ goto top;
+ }
+}
+
+/* Sort a list of dependent packages in alphabetical order */
+static void
+sortpkgs(DEP * d, size_t nd)
+{
+ size_t i;
+ PORT * tmp;
+
+ if (nd == 0)
+ return;
+
+ for (i = nd; i > 0; i--)
+ heapifypkgs(d, nd, i - 1); /* Build a heap */
+ for (i = nd - 1; i > 0; i--) {
+ tmp = d[0].p; /* Extract elements */
+ d[0].p = d[i].p;
+ d[i].p = tmp;
+ heapifypkgs(d, i, 0); /* And re-heapify */
+ }
+}
+
+/* Output an index line for the given port. */
+static void
+printport(PORT * p)
+{
+ size_t i;
+
+ sortpkgs(p->edep, p->n_edep);
+ sortpkgs(p->pdep, p->n_pdep);
+ sortpkgs(p->fdep, p->n_fdep);
+ sortpkgs(p->bdep, p->n_bdep);
+ sortpkgs(p->rdep, p->n_rdep);
+
+ printf("%s|%s|%s|%s|%s|%s|%s|",
+ p->pkgname, p->portdir, p->prefix, p->comment, p->pkgdescr,
+ p->maintainer, p->categories);
+ for (i = 0; i < p->n_bdep; i++)
+ printf("%s%s", i ? " " : "", p->bdep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_rdep; i++)
+ printf("%s%s", i ? " " : "", p->rdep[i].p->pkgname);
+ printf("|");
+ printf("%s|", p->www);
+ for (i = 0; i < p->n_edep; i++)
+ printf("%s%s", i ? " " : "", p->edep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_pdep; i++)
+ printf("%s%s", i ? " " : "", p->pdep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_fdep; i++)
+ printf("%s%s", i ? " " : "", p->fdep[i].p->pkgname);
+ printf("\n");
+}
+
+/*
+ * Algorithm:
+ * 1. Suck in all the data, splitting into fields.
+ * 2. Sort the ports according to port directory.
+ * 3. Using a binary search, translate each dependency from a
+ * port directory name into a pointer to a port.
+ * 4. Recursively follow dependencies, expanding the lists of
+ * pointers as needed (using realloc).
+ * 5. Iterate through the ports, printing them out (remembering
+ * to list the dependent ports in alphabetical order).
+ */
+
+int
+main(int argc, char *argv[])
+{
+ FILE * f;
+ char * line;
+ size_t linelen;
+ PORT ** pp; /* Array of pointers to PORTs */
+ PORT * tmp;
+ size_t pplen; /* Allocated size of array */
+ size_t i;
+
+ if (argc != 2)
+ usage();
+ if ((f = fopen(argv[1], "r")) == NULL)
+ err(1, "fopen(%s)", argv[1]);
+
+ pplen = 1024;
+ if ((pp = malloc(pplen * sizeof(PORT *))) == NULL)
+ err(1, "malloc(pp)");
+
+ /*
+ * 1. Suck in all the data, splitting into fields.
+ */
+ for(i = 0; (line = fgetln(f, &linelen)) != NULL; i++) {
+ if (line[linelen - 1] != '\n')
+ errx(1, "Unterminated line encountered");
+ line[linelen - 1] = 0;
+
+ /* Enlarge array if needed */
+ if (i >= pplen) {
+ pplen *= 2;
+ if ((pp = realloc(pp, pplen * sizeof(PORT *))) == NULL)
+ err(1, "realloc(pp)");
+ }
+
+ pp[i] = portify(line);
+ }
+ /* Reallocate to the correct size */
+ pplen = i;
+ if ((pp = realloc(pp, pplen * sizeof(PORT *))) == NULL)
+ err(1, "realloc(pp)");
+
+ /* Make sure we actually reached the EOF */
+ if (!feof(f))
+ err(1, "fgetln(%s)", argv[1]);
+ /* Close the describes file */
+ if (fclose(f) != 0)
+ err(1, "fclose(%s)", argv[1]);
+
+ /*
+ * 2. Sort the ports according to port directory.
+ */
+ for (i = pplen; i > 0; i--)
+ heapifyports(pp, pplen, i - 1); /* Build a heap */
+ for (i = pplen - 1; i > 0; i--) {
+ tmp = pp[0]; /* Extract elements */
+ pp[0] = pp[i];
+ pp[i] = tmp;
+ heapifyports(pp, i, 0); /* And re-heapify */
+ }
+
+ /*
+ * 3. Using a binary search, translate each dependency from a
+ * port directory name into a pointer to a port.
+ */
+ for (i = 0; i < pplen; i++)
+ translateport(pp, pplen, pp[i]);
+
+ /*
+ * 4. Recursively follow dependencies, expanding the lists of
+ * pointers as needed (using realloc).
+ */
+ for (i = 0; i < pplen; i++)
+ recurse(pp[i]);
+
+ /*
+ * 5. Iterate through the ports, printing them out (remembering
+ * to list the dependent ports in alphabetical order).
+ */
+ for (i = 0; i < pplen; i++)
+ printport(pp[i]);
+
+ return 0;
+}
diff --git a/usr.sbin/portsnap/phttpget/Makefile b/usr.sbin/portsnap/phttpget/Makefile
new file mode 100644
index 0000000..154ff15
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= phttpget
+NO_MAN=
+WARNS?= 6
+
+BINDIR= ${LIBEXECDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/phttpget/phttpget.c b/usr.sbin/portsnap/phttpget/phttpget.c
new file mode 100644
index 0000000..a75c6ea
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/phttpget.c
@@ -0,0 +1,598 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static const char * env_HTTP_PROXY;
+static const char * env_HTTP_USER_AGENT;
+static const char * proxyport;
+
+static struct timeval timo = { 15, 0};
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: phttpget server [file ...]\n");
+ exit(EX_USAGE);
+}
+
+static void
+readenv(void)
+{
+ char * p;
+
+ env_HTTP_PROXY = getenv("HTTP_PROXY");
+ if (env_HTTP_PROXY) {
+ if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
+ env_HTTP_PROXY += 7;
+ p = strchr(env_HTTP_PROXY, ':');
+ if (p != NULL) {
+ *p = 0;
+ proxyport = p + 1;
+ } else
+ proxyport = "3128";
+ }
+
+ env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
+ if (env_HTTP_USER_AGENT == NULL)
+ env_HTTP_USER_AGENT = "phttpget/0.1";
+}
+
+static int
+makerequest(char ** buf, char * path, char * server, int connclose)
+{
+ int buflen;
+
+ buflen = asprintf(buf,
+ "GET %s%s/%s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "User-Agent: %s\r\n"
+ "%s"
+ "\r\n",
+ env_HTTP_PROXY ? "http://" : "",
+ env_HTTP_PROXY ? server : "",
+ path, server, env_HTTP_USER_AGENT,
+ connclose ? "Connection: Close\r\n" : "");
+ if (buflen == -1)
+ err(1, "asprintf");
+ return(buflen);
+}
+
+static int
+readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
+{
+ ssize_t len;
+
+ while (strnstr(resbuf + *resbufpos, "\r\n",
+ *resbuflen - *resbufpos) == NULL) {
+ /* Move buffered data to the start of the buffer */
+ if (*resbufpos != 0) {
+ memmove(resbuf, resbuf + *resbufpos,
+ *resbuflen - *resbufpos);
+ *resbuflen -= *resbufpos;
+ *resbufpos = 0;
+ }
+
+ /* If the buffer is full, complain */
+ if (*resbuflen == BUFSIZ)
+ return -1;
+
+ /* Read more data into the buffer */
+ len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
+ if ((len == -1) && (errno != EINTR))
+ return -1;
+
+ if (len != -1)
+ *resbuflen += len;
+ }
+
+ return 0;
+}
+
+static int
+copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
+ int * resbufpos)
+{
+ ssize_t len;
+
+ while (copylen) {
+ /* Write data from resbuf to fd */
+ len = *resbuflen - *resbufpos;
+ if (copylen < len)
+ len = copylen;
+ if (len > 0) {
+ if (fd != -1)
+ len = write(fd, resbuf + *resbufpos, len);
+ if (len == -1)
+ err(1, "write");
+ *resbufpos += len;
+ copylen -= len;
+ continue;
+ }
+
+ /* Read more data into buffer */
+ len = recv(sd, resbuf, BUFSIZ, 0);
+ if (len == -1) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ } else if (len == 0) {
+ return -2;
+ } else {
+ *resbuflen = len;
+ *resbufpos = 0;
+ }
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints; /* Hints to getaddrinfo */
+ struct addrinfo *res; /* Pointer to server address being used */
+ struct addrinfo *res0; /* Pointer to server addresses */
+ char * resbuf = NULL; /* Response buffer */
+ int resbufpos = 0; /* Response buffer position */
+ int resbuflen = 0; /* Response buffer length */
+ char * eolp; /* Pointer to "\r\n" within resbuf */
+ char * hln0; /* Pointer to start of header line */
+ char * hln; /* Pointer within header line */
+ char * servername; /* Name of server */
+ char * fname = NULL; /* Name of downloaded file */
+ char * reqbuf = NULL; /* Request buffer */
+ int reqbufpos = 0; /* Request buffer position */
+ int reqbuflen = 0; /* Request buffer length */
+ ssize_t len; /* Length sent or received */
+ int nreq = 0; /* Number of next request to send */
+ int nres = 0; /* Number of next reply to receive */
+ int pipelined = 0; /* != 0 if connection in pipelined mode. */
+ int sd = -1; /* Socket descriptor */
+ int sdflags = 0; /* Flags on the socket sd */
+ int fd = -1; /* Descriptor for download target file */
+ int error; /* Error code */
+ int statuscode; /* HTTP Status code */
+ off_t contentlength; /* Value from Content-Length header */
+ int chunked; /* != if transfer-encoding is chunked */
+ off_t clen; /* Chunk length */
+ int firstreq = 0; /* # of first request for this connection */
+
+ /* Check that the arguments are sensible */
+ if (argc < 2)
+ usage();
+
+ /* Read important environment variables */
+ readenv();
+
+ /* Get server name and adjust arg[cv] to point at file names */
+ servername = argv[1];
+ argv += 2;
+ argc -= 2;
+
+ /* Allocate response buffer */
+ resbuf = malloc(BUFSIZ);
+ if (resbuf == NULL)
+ err(1, "malloc");
+
+ /* Look up server */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
+ env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
+ if (error)
+ errx(1, "%s: %s",
+ env_HTTP_PROXY ? env_HTTP_PROXY : servername,
+ gai_strerror(error));
+ if (res0 == NULL)
+ errx(1, "could not look up %s", servername);
+ res = res0;
+
+ /* Do the fetching */
+ while (nres < argc) {
+ /* Make sure we have a connected socket */
+ for (; sd == -1; res = res->ai_next) {
+ /* No addresses left to try :-( */
+ if (res == NULL)
+ errx(1, "Could not connect to %s", servername);
+
+ /* Create a socket... */
+ sd = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol);
+ if (sd == -1)
+ continue;
+
+ /* ... set 15-second timeouts ... */
+ setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
+ (void *)&timo, (socklen_t)sizeof(timo));
+ setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
+ (void *)&timo, (socklen_t)sizeof(timo));
+
+ /* ... and connect to the server. */
+ if(connect(sd, res->ai_addr, res->ai_addrlen)) {
+ close(sd);
+ sd = -1;
+ continue;
+ }
+
+ firstreq = nres;
+ }
+
+ /*
+ * If in pipelined HTTP mode, put socket into non-blocking
+ * mode, since we're probably going to want to try to send
+ * several HTTP requests.
+ */
+ if (pipelined) {
+ sdflags = fcntl(sd, F_GETFL);
+ if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
+ err(1, "fcntl");
+ }
+
+ /* Construct requests and/or send them without blocking */
+ while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
+ /* If not in the middle of a request, make one */
+ if (reqbuf == NULL) {
+ reqbuflen = makerequest(&reqbuf, argv[nreq],
+ servername, (nreq == argc - 1));
+ reqbufpos = 0;
+ }
+
+ /* If in pipelined mode, try to send the request */
+ if (pipelined) {
+ while (reqbufpos < reqbuflen) {
+ len = send(sd, reqbuf + reqbufpos,
+ reqbuflen - reqbufpos, 0);
+ if (len == -1)
+ break;
+ reqbufpos += len;
+ }
+ if (reqbufpos < reqbuflen) {
+ if (errno != EAGAIN)
+ goto conndied;
+ break;
+ } else {
+ free(reqbuf);
+ reqbuf = NULL;
+ nreq++;
+ }
+ }
+ }
+
+ /* Put connection back into blocking mode */
+ if (pipelined) {
+ if (fcntl(sd, F_SETFL, sdflags) == -1)
+ err(1, "fcntl");
+ }
+
+ /* Do we need to blocking-send a request? */
+ if (nres == nreq) {
+ while (reqbufpos < reqbuflen) {
+ len = send(sd, reqbuf + reqbufpos,
+ reqbuflen - reqbufpos, 0);
+ if (len == -1)
+ goto conndied;
+ reqbufpos += len;
+ }
+ free(reqbuf);
+ reqbuf = NULL;
+ nreq++;
+ }
+
+ /* Scan through the response processing headers. */
+ statuscode = 0;
+ contentlength = -1;
+ chunked = 0;
+ do {
+ /* Get a header line */
+ error = readln(sd, resbuf, &resbuflen, &resbufpos);
+ if (error)
+ goto conndied;
+ hln0 = hln = resbuf + resbufpos;
+ eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
+ resbufpos = (eolp - resbuf) + 2;
+ *eolp = '\0';
+
+ /* Make sure it doesn't contain a NUL character */
+ if (strchr(hln, '\0') != eolp)
+ goto conndied;
+
+ if (statuscode == 0) {
+ /* The first line MUST be HTTP/1.x xxx ... */
+ if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
+ ! isdigit(hln[7]))
+ goto conndied;
+
+ /*
+ * If the minor version number isn't zero,
+ * then we can assume that pipelining our
+ * requests is OK -- as long as we don't
+ * see a "Connection: close" line later
+ * and we either have a Content-Length or
+ * Transfer-Encoding: chunked header to
+ * tell us the length.
+ */
+ if (hln[7] != '0')
+ pipelined = 1;
+
+ /* Skip over the minor version number */
+ hln = strchr(hln + 7, ' ');
+ if (hln == NULL)
+ goto conndied;
+ else
+ hln++;
+
+ /* Read the status code */
+ while (isdigit(*hln)) {
+ statuscode = statuscode * 10 +
+ *hln - '0';
+ hln++;
+ }
+
+ if (statuscode < 100 || statuscode > 599)
+ goto conndied;
+
+ /* Ignore the rest of the line */
+ continue;
+ }
+
+ /* Check for "Connection: close" header */
+ if (strncmp(hln, "Connection:", 11) == 0) {
+ hln += 11;
+ if (strstr(hln, "close") != NULL)
+ pipelined = 0;
+
+ /* Next header... */
+ continue;
+ }
+
+ /* Check for "Content-Length:" header */
+ if (strncmp(hln, "Content-Length:", 15) == 0) {
+ hln += 15;
+ contentlength = 0;
+
+ /* Find the start of the length */
+ while (!isdigit(*hln) && (*hln != '\0'))
+ hln++;
+
+ /* Compute the length */
+ while (isdigit(*hln)) {
+ if (contentlength > INT_MAX / 10) {
+ /* Nasty people... */
+ goto conndied;
+ }
+ contentlength = contentlength * 10 +
+ *hln - '0';
+ hln++;
+ }
+
+ /* Next header... */
+ continue;
+ }
+
+ /* Check for "Transfer-Encoding: chunked" header */
+ if (strncmp(hln, "Transfer-Encoding:", 18) == 0) {
+ hln += 18;
+ if (strstr(hln, "chunked") != NULL)
+ chunked = 1;
+
+ /* Next header... */
+ continue;
+ }
+
+ /* We blithely ignore any other header lines */
+
+ /* No more header lines */
+ if (strlen(hln) == 0) {
+ /*
+ * If the status code was 1xx, then there will
+ * be a real header later. Servers may emit
+ * 1xx header blocks at will, but since we
+ * don't expect one, we should just ignore it.
+ */
+ if (100 <= statuscode && statuscode <= 199) {
+ statuscode = 0;
+ continue;
+ }
+
+ /* End of header; message body follows */
+ break;
+ }
+ } while (1);
+
+ /* No message body for 204 or 304 */
+ if (statuscode == 204 || statuscode == 304) {
+ nres++;
+ continue;
+ }
+
+ /*
+ * There should be a message body coming, but we only want
+ * to send it to a file if the status code is 200
+ */
+ if (statuscode == 200) {
+ /* Generate a file name for the download */
+ fname = strrchr(argv[nres], '/');
+ if (fname == NULL)
+ fname = argv[nres];
+ else
+ fname++;
+ if (strlen(fname) == 0)
+ errx(1, "Cannot obtain file name from %s\n",
+ argv[nres]);
+
+ fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd == -1)
+ errx(1, "open(%s)", fname);
+ };
+
+ /* Read the message and send data to fd if appropriate */
+ if (chunked) {
+ /* Handle a chunked-encoded entity */
+
+ /* Read chunks */
+ do {
+ error = readln(sd, resbuf, &resbuflen,
+ &resbufpos);
+ if (error)
+ goto conndied;
+ hln = resbuf + resbufpos;
+ eolp = strstr(hln, "\r\n");
+ resbufpos = (eolp - resbuf) + 2;
+
+ clen = 0;
+ while (isxdigit(*hln)) {
+ if (clen > INT_MAX / 16) {
+ /* Nasty people... */
+ goto conndied;
+ }
+ if (isdigit(*hln))
+ clen = clen * 16 + *hln - '0';
+ else
+ clen = clen * 16 + 10 +
+ tolower(*hln) - 'a';
+ hln++;
+ }
+
+ error = copybytes(sd, fd, clen, resbuf,
+ &resbuflen, &resbufpos);
+ if (error) {
+ goto conndied;
+ }
+ } while (clen != 0);
+
+ /* Read trailer and final CRLF */
+ do {
+ error = readln(sd, resbuf, &resbuflen,
+ &resbufpos);
+ if (error)
+ goto conndied;
+ hln = resbuf + resbufpos;
+ eolp = strstr(hln, "\r\n");
+ resbufpos = (eolp - resbuf) + 2;
+ } while (hln != eolp);
+ } else if (contentlength != -1) {
+ error = copybytes(sd, fd, contentlength, resbuf,
+ &resbuflen, &resbufpos);
+ if (error)
+ goto conndied;
+ } else {
+ /*
+ * Not chunked, and no content length header.
+ * Read everything until the server closes the
+ * socket.
+ */
+ error = copybytes(sd, fd, INT_MAX, resbuf,
+ &resbuflen, &resbufpos);
+ if (error == -1)
+ goto conndied;
+ pipelined = 0;
+ }
+
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+
+ fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
+ statuscode);
+ if (statuscode == 200)
+ fprintf(stderr, "OK\n");
+ else if (statuscode < 300)
+ fprintf(stderr, "Successful (ignored)\n");
+ else if (statuscode < 400)
+ fprintf(stderr, "Redirection (ignored)\n");
+ else
+ fprintf(stderr, "Error (ignored)\n");
+
+ /* We've finished this file! */
+ nres++;
+
+ /*
+ * If necessary, clean up this connection so that we
+ * can start a new one.
+ */
+ if (pipelined == 0)
+ goto cleanupconn;
+ continue;
+
+conndied:
+ /*
+ * Something went wrong -- our connection died, the server
+ * sent us garbage, etc. If this happened on the first
+ * request we sent over this connection, give up. Otherwise,
+ * close this connection, open a new one, and reissue the
+ * request.
+ */
+ if (nres == firstreq)
+ errx(1, "Connection failure");
+
+cleanupconn:
+ /*
+ * Clean up our connection and keep on going
+ */
+ shutdown(sd, SHUT_RDWR);
+ close(sd);
+ sd = -1;
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ if (reqbuf != NULL) {
+ free(reqbuf);
+ reqbuf = NULL;
+ }
+ nreq = nres;
+ res = res0;
+ pipelined = 0;
+ resbufpos = resbuflen = 0;
+ continue;
+ }
+
+ free(resbuf);
+ freeaddrinfo(res0);
+
+ return 0;
+}
diff --git a/usr.sbin/portsnap/portsnap/Makefile b/usr.sbin/portsnap/portsnap/Makefile
new file mode 100644
index 0000000..53ae4d7
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=portsnap.sh
+MAN8= portsnap.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/portsnap/portsnap.8 b/usr.sbin/portsnap/portsnap/portsnap.8
new file mode 100644
index 0000000..9e1aa0f
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/portsnap.8
@@ -0,0 +1,208 @@
+\.\"-
+.\" Copyright 2004-2005 Colin Percival
+.\" All rights reserved
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted providing that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+.\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+.\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 30, 2005
+.Dt PORTSNAP 8
+.Os FreeBSD
+.Sh NAME
+.Nm portsnap
+.Nd fetch and extract compressed snapshots of the ports tree
+.Sh SYNOPSIS
+.Nm
+.Op Fl I
+.Op Fl d Ar workdir
+.Op Fl f Ar conffile
+.Op Fl k Ar KEY
+.Op Fl p Ar portsdir
+.Op Fl s Ar server
+.Cm command
+.Op Ar path
+.Sh DESCRIPTION
+The
+.Nm
+tool is used to fetch and update compressed snapshots
+of the FreeBSD ports tree, and extract and update an
+uncompressed ports tree.
+.Sh OPTIONS
+The following options are supported:
+.Bl -tag -width "-f conffile"
+.It Fl d Ar workdir
+Store working files (e.g. downloaded updates) in
+.Ar workdir .
+(default:
+.Pa /var/db/portsnap ,
+or as given in the configuration file.)
+.It Fl f Ar conffile
+Read the configuration from from
+.Ar conffile .
+(default:
+.Pa /etc/portsnap.conf )
+.It Fl I
+For the
+.Cm update
+command, update INDEX files, but not the rest of the ports tree.
+.It Fl k Ar KEY
+Expect a public key with given SHA256 hash.
+(default: read value from configuration file.)
+.It Fl p Ar portsdir
+When extracting or updating an uncompressed snapshot,
+operate on the directory
+.Ar portsdir .
+(default:
+.Pa /usr/ports/ ,
+or as given in the configuration file.)
+.It Fl s Ar server
+Fetch files from the specified server or server pool.
+(default: portsnap.FreeBSD.org , or as given in the
+configuration file.)
+.It path
+For
+.Cm extract
+command only, operate only on parts of the ports tree starting with
+.Ar path .
+(e.g.
+.Nm
+.cm extract
+.Ar sysutils/port
+would extract sysutils/portsman, sysutils/portsnap,
+sysutils/portupgrade, etc.)
+.El
+.Sh COMMANDS
+The
+.Cm command
+can be any one of the following:
+.Pp
+.Bl -tag -width "-f conffile"
+.It fetch
+Fetch a compressed snapshot of the ports tree, or update
+the existing snapshot.
+This command should only be used interactively; for
+non-interactive use, you should use the
+.Cm cron
+command.
+.It cron
+Sleep a random amount of time, then operate as if the
+.Cm fetch
+command was specified.
+As the name suggests, this command is designed for running
+from
+.Xr cron 8 ;
+the random delay serves to minimize the probability that
+a large number of machines will simultaneously attempt to
+fetch updates.
+.It extract
+Extract a ports tree, replacing existing files and directories.
+NOTE: This will remove anything occupying the location where
+files or directories are being extracted; in particular, any
+changes made locally to the ports tree (for example, adding new
+patches) will be silently obliterated.
+.Pp
+Only run this command to initialize your portsnap-maintained
+ports tree for the first time, if you wish to start over with
+a clean, completely unmodified tree, or if you wish to extract
+a specific part of the tree (using the
+.Ar path
+option).
+.It update
+Update a ports tree extracted using the
+.Cm extract
+command.
+You must run this command to apply changes to your ports tree
+after downloading updates via the
+.Cm fetch
+or
+.Cm cron
+commands.
+Again, note that in the parts of the ports tree which are being
+updated, any local changes or additions will be removed.
+.El
+.Sh TIPS
+.Bl -bullet
+.It
+If your clock is set to local time, adding the line
+.Pp
+.Dl 0 3 * * * root /usr/sbin/portsnap cron
+.Pp
+to /etc/crontab is a good way to make sure you always have
+an up-to-date snapshot of the ports tree available which
+can quickly be extracted into
+.Pa /usr/ports .
+If your clock is set to UTC, please pick a random time other
+than 3AM, to avoid overly imposing an uneven load on the
+server(s) hosting the snapshots.
+.It
+Running
+.Nm
+.Cm update
+from
+.Xr cron 8
+is a bad idea -- if you're ever installing or updating a
+port at the time the cron job runs, you'll probably end up
+in a mess when
+.Cm
+updates or removes files which are being used by the port
+build.
+However, running
+.Nm
+.Fl I
+.Cm update
+is probably safe, and can be used together with
+.Xr portversion 1
+to identify installed software which is out of date.
+.It
+If you wish to use
+.Nm
+to keep a large number of machines up to date, you may wish
+to set up a caching HTTP proxy. Since
+.Nm
+uses
+.Xr fetch 1
+to download updates, setting the
+.Ev HTTP_PROXY
+environment variable will direct it to fetch updates from
+the given proxy.
+This is much more efficient than
+.Em mirroring
+the files on the portsnap server, since the vast majority
+of files are not needed by any particular client.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/portsnap.conf"
+.It /etc/portsnap.conf
+Default location of the portsnap configuration file.
+.It /var/db/portsnap
+Default location where compressed snapshots are stored.
+.It /usr/ports
+Default location where the ports tree is extracted.
+.El
+.Sh SEE ALSO
+.Xr fetch 1
+.Xr fetch 3
+.Xr portsnap.conf 5
+.Xr sha256 8
+.Sh AUTHORS
+.An Colin Percival Aq cperciva@FreeBSD.org
diff --git a/usr.sbin/portsnap/portsnap/portsnap.sh b/usr.sbin/portsnap/portsnap/portsnap.sh
new file mode 100644
index 0000000..d73965b
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/portsnap.sh
@@ -0,0 +1,901 @@
+#!/bin/sh
+
+#-
+# Copyright 2004-2005 Colin Percival
+# All rights reserved
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted providing that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+# $FreeBSD$
+
+#### Usage function -- called from command-line handling code.
+
+# Usage instructions. Options not listed:
+# --debug -- don't filter output from utilities
+# --no-stats -- don't show progress statistics while fetching files
+usage() {
+ cat <<EOF
+usage: `basename $0` [options] command [path]
+
+Options:
+ -d workdir -- Store working files in workdir
+ (default: /var/db/portsnap/)
+ -f conffile -- Read configuration options from conffile
+ (default: /etc/portsnap.conf)
+ -I -- Update INDEX only. (update command only)
+ -k KEY -- Trust an RSA key with SHA256 hash of KEY
+ -p portsdir -- Location of uncompressed ports tree
+ (default: /usr/ports/)
+ -s server -- Server from which to fetch updates.
+ (default: portsnap.FreeBSD.org)
+ path -- Extract only parts of the tree starting with the given
+ string. (extract command only)
+Commands:
+ fetch -- Fetch a compressed snapshot of the ports tree,
+ or update an existing snapshot.
+ cron -- Sleep rand(3600) seconds, and then fetch updates.
+ extract -- Extract snapshot of ports tree, replacing existing
+ files and directories.
+ update -- Update ports tree to match current snapshot, replacing
+ files and directories which have changed.
+EOF
+ exit 0
+}
+
+#### Parameter handling functions.
+
+# Initialize parameters to null, just in case they're
+# set in the environment.
+init_params() {
+ KEYPRINT=""
+ EXTRACTPATH=""
+ WORKDIR=""
+ PORTSDIR=""
+ CONFFILE=""
+ COMMAND=""
+ QUIETREDIR=""
+ QUIETFLAG=""
+ STATSREDIR=""
+ NDEBUG=""
+ DDSTATS=""
+ INDEXONLY=""
+ SERVERNAME=""
+}
+
+# Parse the command line
+parse_cmdline() {
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ -d)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${WORKDIR}" ]; then usage; fi
+ shift; WORKDIR="$1"
+ ;;
+ --debug)
+ QUIETREDIR="/dev/stderr"
+ STATSREDIR="/dev/stderr"
+ QUIETFLAG=" "
+ NDEBUG=" "
+ DDSTATS=".."
+ ;;
+ -f)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${CONFFILE}" ]; then usage; fi
+ shift; CONFFILE="$1"
+ ;;
+ -h | --help | help)
+ usage
+ ;;
+ -I)
+ INDEXONLY="YES"
+ ;;
+ -k)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${KEYPRINT}" ]; then usage; fi
+ shift; KEYPRINT="$1"
+ ;;
+ --no-stats)
+ if [ -z "${STATSREDIR}" ]; then
+ STATSREDIR="/dev/null"
+ DDSTATS=".. "
+ fi
+ ;;
+ -p)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${PORTSDIR}" ]; then usage; fi
+ shift; PORTSDIR="$1"
+ ;;
+ -s)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${SERVERNAME}" ]; then usage; fi
+ shift; SERVERNAME="$1"
+ ;;
+ cron | extract | fetch | update)
+ if [ ! -z "${COMMAND}" ]; then usage; fi
+ COMMAND="$1"
+ ;;
+ *)
+ if [ $# -gt 1 ]; then usage; fi
+ if [ "${COMMAND}" = "extract" ]; then usage; fi
+ EXTRACTPATH="$1"
+ ;;
+ esac
+ shift
+ done
+
+ if [ -z "${COMMAND}" ]; then
+ usage
+ fi
+}
+
+# If CONFFILE was specified at the command-line, make
+# sure that it exists and is readable.
+sanity_conffile() {
+ if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
+ echo -n "File does not exist "
+ echo -n "or is not readable: "
+ echo ${CONFFILE}
+ exit 1
+ fi
+}
+
+# If a configuration file hasn't been specified, use
+# the default value (/etc/portsnap.conf)
+default_conffile() {
+ if [ -z "${CONFFILE}" ]; then
+ CONFFILE="/etc/portsnap.conf"
+ fi
+}
+
+# Read {KEYPRINT, SERVERNAME, WORKDIR, PORTSDIR} from the configuration
+# file if they haven't already been set. If the configuration
+# file doesn't exist, do nothing.
+parse_conffile() {
+ if [ -r "${CONFFILE}" ]; then
+ for X in KEYPRINT WORKDIR PORTSDIR SERVERNAME; do
+ eval _=\$${X}
+ if [ -z "${_}" ]; then
+ eval ${X}=`grep "^${X}=" "${CONFFILE}" |
+ cut -f 2- -d '=' | tail -1`
+ fi
+ done
+ fi
+}
+
+# If parameters have not been set, use default values
+default_params() {
+ _QUIETREDIR="/dev/null"
+ _QUIETFLAG="-q"
+ _STATSREDIR="/dev/stdout"
+ _WORKDIR="/var/db/portsnap"
+ _PORTSDIR="/usr/ports"
+ _NDEBUG="-n"
+ for X in QUIETREDIR QUIETFLAG STATSREDIR WORKDIR PORTSDIR NDEBUG; do
+ eval _=\$${X}
+ eval __=\$_${X}
+ if [ -z "${_}" ]; then
+ eval ${X}=${__}
+ fi
+ done
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for fetching files. Also chdir into
+# the working directory.
+fetch_check_params() {
+ export HTTP_USER_AGENT="portsnap (${COMMAND}, `uname -r`)"
+
+ _SERVERNAME_z=\
+"SERVERNAME must be given via command line or configuration file."
+ _KEYPRINT_z="Key must be given via -k option or configuration file."
+ _KEYPRINT_bad="Invalid key fingerprint: "
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+
+ if [ -z "${SERVERNAME}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_SERVERNAME_z}"
+ exit 1
+ fi
+ if [ -z "${KEYPRINT}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_KEYPRINT_z}"
+ exit 1
+ fi
+ if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
+ echo -n "`basename $0`: "
+ echo -n "${_KEYPRINT_bad}"
+ echo ${KEYPRINT}
+ exit 1
+ fi
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ cd ${WORKDIR} || exit 1
+
+ BSPATCH=/usr/bin/bspatch
+ SHA256=/sbin/sha256
+ PHTTPGET=/usr/libexec/phttpget
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for extracting or updating ${PORTSDIR}
+extract_check_params() {
+ _WORKDIR_bad="Directory does not exist: "
+ _PORTSDIR_bad="Directory does not exist or is not writable: "
+
+ if ! [ -d "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ if ! [ -d "${PORTSDIR}" -a -w "${PORTSDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_PORTSDIR_bad}"
+ echo ${PORTSDIR}
+ exit 1
+ fi
+
+ if ! [ -d "${WORKDIR}/files" -a -r "${WORKDIR}/tag" \
+ -a -r "${WORKDIR}/INDEX" -a -r "${WORKDIR}/tINDEX" ]; then
+ echo "No snapshot available. Try running"
+ echo "# `basename $0` fetch"
+ exit 1
+ fi
+
+ MKINDEX=/usr/libexec/make_index
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for updating ${PORTSDIR}
+update_check_params() {
+ extract_check_params
+
+ if ! [ -r ${PORTSDIR}/.portsnap.INDEX ]; then
+ echo "${PORTSDIR} was not created by portsnap."
+ echo -n "You must run '`basename $0` extract' before "
+ echo "running '`basename $0` update'."
+ exit 1
+ fi
+
+}
+
+#### Core functionality -- the actual work gets done here
+
+# Use an SRV query to pick a server. If the SRV query doesn't provide
+# a useful answer, use the server name specified by the user.
+# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
+# from that; or if no servers are returned, use ${SERVERNAME}.
+# This allows a user to specify "portsnap.freebsd.org" (in which case
+# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
+# (in which case portsnap will use that particular server, since there
+# won't be an SRV entry for that name).
+#
+# We don't implement the recommendations from RFC 2782 completely, since
+# we are only looking to pick a single server -- the recommendations are
+# targetted at applications which obtain a list of servers and then try
+# each in turn, but we are instead just going to pick one server and let
+# the user re-run portsnap if a broken server was selected.
+#
+# We also ignore the Port field, since we are always going to use port 80.
+fetch_pick_server() {
+ echo -n "Looking up ${SERVERNAME} mirrors..."
+
+# Issue the SRV query and pull out the Priority, Weight, and Target fields.
+ host -t srv "_http._tcp.${SERVERNAME}" |
+ grep -E "^_http._tcp.${SERVERNAME} has SRV record" |
+ cut -f 5,6,8 -d ' ' > serverlist
+
+# If no records, give up -- we'll just use the server name we were given.
+ if [ `wc -l < serverlist` -eq 0 ]; then
+ echo " none found."
+ return
+ fi
+
+# Find the highest priority level (lowest numeric value).
+ SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
+
+# Add up the weights of the response lines at that priority level.
+ SRV_WSUM=0;
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_WSUM=$(($SRV_WSUM + $SRV_W))
+ ;;
+ esac
+ done < serverlist
+
+# If all the weights are 0, pretend that they are all 1 instead.
+ if [ ${SRV_WSUM} -eq 0 ]; then
+ SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
+ SRV_W_ADD=1
+ else
+ SRV_W_ADD=0
+ fi
+
+# Pick a random value between 1 and the sum of the weights
+ SRV_RND=`jot -r 1 1 ${SRV_WSUM}`
+
+# Read through the list of mirrors and set SERVERNAME
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_W=$(($SRV_W + $SRV_W_ADD))
+ if [ $SRV_RND -le $SRV_W ]; then
+ SERVERNAME=`echo $X | cut -f 3 -d ' '`
+ break
+ else
+ SRV_RND=$(($SRV_RND - $SRV_W))
+ fi
+ ;;
+ esac
+ done < serverlist
+
+ echo " using ${SERVERNAME}"
+}
+
+# Check that we have a public key with an appropriate hash, or
+# fetch the key if it doesn't exist.
+fetch_key() {
+ if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ return
+ fi
+
+ echo -n "Fetching public key... "
+ rm -f pub.ssl
+ fetch ${QUIETFLAG} http://${SERVERNAME}/pub.ssl \
+ 2>${QUIETREDIR} || true
+ if ! [ -r pub.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+ if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ echo "key has incorrect hash."
+ rm -f pub.ssl
+ return 1
+ fi
+ echo "done."
+}
+
+# Fetch a snapshot tag
+fetch_tag() {
+ rm -f snapshot.ssl tag.new
+
+ echo ${NDEBUG} "Fetching snapshot tag... "
+ fetch ${QUIETFLAG} http://${SERVERNAME}/$1.ssl
+ 2>${QUIETREDIR} || true
+ if ! [ -r $1.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+
+ openssl rsautl -pubin -inkey pub.ssl -verify \
+ < $1.ssl > tag.new 2>${QUIETREDIR} || true
+ rm $1.ssl
+
+ if ! [ `wc -l < tag.new` = 1 ] ||
+ ! grep -qE "^portsnap\|[0-9]{10}\|[0-9a-f]{64}" tag.new; then
+ echo "invalid snapshot tag."
+ return 1
+ fi
+
+ echo "done."
+
+ SNAPSHOTDATE=`cut -f 2 -d '|' < tag.new`
+ SNAPSHOTHASH=`cut -f 3 -d '|' < tag.new`
+}
+
+# Sanity-check the date on a snapshot tag
+fetch_snapshot_tagsanity() {
+ if [ `date "+%s"` -gt `expr ${SNAPSHOTDATE} + 31536000` ]; then
+ echo "Snapshot appears to be more than a year old!"
+ echo "(Is the system clock correct?)"
+ echo "Cowarly refusing to proceed any further."
+ return 1
+ fi
+ if [ `date "+%s"` -lt `expr ${SNAPSHOTDATE} - 86400` ]; then
+ echo -n "Snapshot appears to have been created more than "
+ echo "one day into the future!"
+ echo "(Is the system clock correct?)"
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+}
+
+# Sanity-check the date on a snapshot update tag
+fetch_update_tagsanity() {
+ fetch_snapshot_tagsanity || return 1
+
+ if [ ${OLDSNAPSHOTDATE} -gt ${SNAPSHOTDATE} ]; then
+ echo -n "Latest snapshot on server is "
+ echo "older than what we already have!"
+ echo -n "Cowardly refusing to downgrade from "
+ date -r ${OLDSNAPSHOTDATE}
+ echo -n "to `date -r ${SNAPSHOTDATE}`."
+ return 1
+ fi
+}
+
+# Compare old and new tags; return 1 if update is unnecessary
+fetch_update_neededp() {
+ if [ ${OLDSNAPSHOTDATE} -eq ${SNAPSHOTDATE} ]; then
+ echo -n "Latest snapshot on server matches "
+ echo "what we already have."
+ echo "No updates needed."
+ rm tag.new
+ return 1
+ fi
+ if [ ${OLDSNAPSHOTHASH} = ${SNAPSHOTHASH} ]; then
+ echo -n "Ports tree hasn't changed since "
+ echo "last snapshot."
+ echo "No updates needed."
+ rm tag.new
+ return 1
+ fi
+
+ return 0
+}
+
+# Fetch snapshot metadata file
+fetch_metadata() {
+ rm -f ${SNAPSHOTHASH} tINDEX.new
+
+ echo ${NDEBUG} "Fetching snapshot metadata... "
+ fetch ${QUIETFLAG} http://${SERVERNAME}/t/${SNAPSHOTHASH}
+ 2>${QUIETREDIR} || return
+ if [ `${SHA256} -q ${SNAPSHOTHASH}` != ${SNAPSHOTHASH} ]; then
+ echo "snapshot metadata corrupt."
+ return 1
+ fi
+ mv ${SNAPSHOTHASH} tINDEX.new
+ echo "done."
+}
+
+# Warn user about bogus metadata
+fetch_metadata_freakout() {
+ echo
+ echo "Portsnap metadata is correctly signed, but contains"
+ echo "at least one line which appears bogus."
+ echo "Cowardly refusing to proceed any further."
+}
+
+# Sanity-check a snapshot metadata file
+fetch_metadata_sanity() {
+ if grep -qvE "^[0-9A-Z.]+\|[0-9a-f]{64}$" tINDEX.new; then
+ fetch_metadata_freakout
+ return 1
+ fi
+ if [ `look INDEX tINDEX.new | wc -l` != 1 ]; then
+ echo
+ echo "Portsnap metadata appears bogus."
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+}
+
+# Take a list of ${oldhash}|${newhash} and output a list of needed patches
+fetch_make_patchlist() {
+ grep -vE "^([0-9a-f]{64})\|\1$" |
+ while read LINE; do
+ X=`echo ${LINE} | cut -f 1 -d '|'`
+ Y=`echo ${LINE} | cut -f 2 -d '|'`
+ if [ -f "files/${Y}.gz" ]; then continue; fi
+ if [ ! -f "files/${X}.gz" ]; then continue; fi
+ echo "${LINE}"
+ done
+}
+
+# Print user-friendly progress statistics
+fetch_progress() {
+ LNC=0
+ while read x; do
+ LNC=$(($LNC + 1))
+ if [ $(($LNC % 10)) = 0 ]; then
+ echo -n $FN2
+ elif [ $(($LNC % 2)) = 0 ]; then
+ echo -n .
+ fi
+ done
+ echo -n " "
+}
+
+# Sanity-check an index file
+fetch_index_sanity() {
+ if grep -qvE "^[-_+./@0-9A-Za-z]+\|[0-9a-f]{64}$" INDEX.new ||
+ fgrep -q "./" INDEX.new; then
+ fetch_metadata_freakout
+ return 1
+ fi
+}
+
+# Verify a list of files
+fetch_snapshot_verify() {
+ while read F; do
+ if [ `gunzip -c snap/${F} | ${SHA256} -q` != ${F} ]; then
+ echo "snapshot corrupt."
+ return 1
+ fi
+ done
+ return 0
+}
+
+# Fetch a snapshot tarball, extract, and verify.
+fetch_snapshot() {
+ fetch_tag snapshot || return 1
+ fetch_snapshot_tagsanity || return 1
+ fetch_metadata || return 1
+ fetch_metadata_sanity || return 1
+
+ rm -f ${SNAPSHOTHASH}.tgz
+ rm -rf snap/
+
+# Don't ask fetch(1) to be quiet -- downloading a snapshot of ~ 35MB will
+# probably take a while, so the progrees reports that fetch(1) generates
+# will be useful for keeping the users' attention from drifting.
+ echo "Fetching snapshot generated at `date -r ${SNAPSHOTDATE}`:"
+ fetch http://${SERVERNAME}/s/${SNAPSHOTHASH}.tgz || return 1
+
+ echo -n "Extracting snapshot... "
+ tar -xzf ${SNAPSHOTHASH}.tgz snap/ || return 1
+ rm ${SNAPSHOTHASH}.tgz
+ echo "done."
+
+ echo -n "Verifying snapshot integrity... "
+# Verify the metadata files
+ cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1
+# Extract the index
+ rm -f INDEX.new
+ gunzip -c snap/`look INDEX tINDEX.new |
+ cut -f 2 -d '|'`.gz > INDEX.new
+ fetch_index_sanity || return 1
+# Verify the snapshot contents
+ cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1
+ echo "done."
+
+# Move files into their proper locations
+ rm -f tag INDEX tINDEX
+ rm -rf files
+ mv tag.new tag
+ mv tINDEX.new tINDEX
+ mv INDEX.new INDEX
+ mv snap/ files/
+
+ return 0
+}
+
+# Update a compressed snapshot
+fetch_update() {
+ rm -f patchlist diff OLD NEW filelist INDEX.new
+
+ OLDSNAPSHOTDATE=`cut -f 2 -d '|' < tag`
+ OLDSNAPSHOTHASH=`cut -f 3 -d '|' < tag`
+
+ fetch_tag latest || return 1
+ fetch_update_tagsanity || return 1
+ fetch_update_neededp || return 0
+ fetch_metadata || return 1
+ fetch_metadata_sanity || return 1
+
+ echo -n "Updating from `date -r ${OLDSNAPSHOTDATE}` "
+ echo "to `date -r ${SNAPSHOTDATE}`."
+
+# Generate a list of wanted metadata patches
+ join -t '|' -o 1.2,2.2 tINDEX tINDEX.new |
+ fetch_make_patchlist > patchlist
+
+# Attempt to fetch metadata patches
+ echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
+ echo ${NDEBUG} "metadata patches.${DDSTATS}"
+ tr '|' '-' < patchlist |
+ lam -s "tp/" - -s ".gz" |
+ xargs ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress
+ echo "done."
+
+# Attempt to apply metadata patches
+ echo -n "Applying metadata patches... "
+ while read LINE; do
+ X=`echo ${LINE} | cut -f 1 -d '|'`
+ Y=`echo ${LINE} | cut -f 2 -d '|'`
+ if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
+ gunzip -c < ${X}-${Y}.gz > diff
+ gunzip -c < files/${X}.gz > OLD
+ cut -c 2- diff | join -t '|' -v 2 - OLD > ptmp
+ grep '^\+' diff | cut -c 2- |
+ sort -k 1,1 -t '|' -m - ptmp > NEW
+ if [ `${SHA256} -q NEW` = ${Y} ]; then
+ mv NEW files/${Y}
+ gzip -n files/${Y}
+ fi
+ rm -f diff OLD NEW ${X}-${Y}.gz ptmp
+ done < patchlist 2>${QUIETREDIR}
+ echo "done."
+
+# Update metadata without patches
+ join -t '|' -v 2 tINDEX tINDEX.new |
+ cut -f 2 -d '|' /dev/stdin patchlist |
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done > filelist
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "metadata files... "
+ lam -s "f/" - -s ".gz" < filelist |
+ xargs ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ while read Y; do
+ if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "metadata is corrupt."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+
+# Extract the index
+ gunzip -c files/`look INDEX tINDEX.new |
+ cut -f 2 -d '|'`.gz > INDEX.new
+ fetch_index_sanity || return 1
+
+# Generate a list of wanted ports patches
+ join -t '|' -o 1.2,2.2 INDEX INDEX.new |
+ fetch_make_patchlist > patchlist
+
+# Attempt to fetch ports patches
+ echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
+ echo ${NDEBUG} "patches.${DDSTATS}"
+ tr '|' '-' < patchlist | lam -s "bp/" - |
+ xargs ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress
+ echo "done."
+
+# Attempt to apply ports patches
+ echo -n "Applying patches... "
+ while read LINE; do
+ X=`echo ${LINE} | cut -f 1 -d '|'`
+ Y=`echo ${LINE} | cut -f 2 -d '|'`
+ if [ ! -f "${X}-${Y}" ]; then continue; fi
+ gunzip -c < files/${X}.gz > OLD
+ ${BSPATCH} OLD NEW ${X}-${Y}
+ if [ `${SHA256} -q NEW` = ${Y} ]; then
+ mv NEW files/${Y}
+ gzip -n files/${Y}
+ fi
+ rm -f diff OLD NEW ${X}-${Y}
+ done < patchlist 2>${QUIETREDIR}
+ echo "done."
+
+# Update ports without patches
+ join -t '|' -v 2 INDEX INDEX.new |
+ cut -f 2 -d '|' /dev/stdin patchlist |
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done > filelist
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "new ports or files... "
+ lam -s "f/" - -s ".gz" < filelist |
+ xargs ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ while read Y; do
+ if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "snapshot is corrupt."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+
+# Remove files which are no longer needed
+ cut -f 2 -d '|' tINDEX INDEX | sort > oldfiles
+ cut -f 2 -d '|' tINDEX.new INDEX.new | sort | comm -13 - oldfiles |
+ lam -s "files/" - -s ".gz" | xargs rm -f
+ rm patchlist filelist oldfiles
+
+# We're done!
+ mv INDEX.new INDEX
+ mv tINDEX.new tINDEX
+ mv tag.new tag
+
+ return 0
+}
+
+# Do the actual work involved in "fetch" / "cron".
+fetch_run() {
+ fetch_pick_server
+
+ fetch_key || return 1
+
+ if ! [ -d files -a -r tag -a -r INDEX -a -r tINDEX ]; then
+ fetch_snapshot || return 1
+ fi
+ fetch_update || return 1
+}
+
+# Build a ports INDEX file
+extract_make_index() {
+ gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX |
+ cut -f 2 -d '|'`.gz" | ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2
+}
+
+# Create INDEX, INDEX-5, INDEX-6
+extract_indices() {
+ echo -n "Building new INDEX files... "
+ extract_make_index DESCRIBE.4 INDEX || return 1
+ extract_make_index DESCRIBE.5 INDEX-5 || return 1
+ extract_make_index DESCRIBE.6 INDEX-6 || return 1
+ echo "done."
+}
+
+# Create .portsnap.INDEX
+extract_metadata() {
+ sort ${WORKDIR}/INDEX > ${PORTSDIR}/.portsnap.INDEX
+}
+
+# Do the actual work involved in "extract"
+extract_run() {
+ grep "^${EXTRACTPATH}" ${WORKDIR}/INDEX | while read LINE; do
+ FILE=`echo ${LINE} | cut -f 1 -d '|'`
+ HASH=`echo ${LINE} | cut -f 2 -d '|'`
+ echo ${PORTSDIR}/${FILE}
+ if ! [ -r "${WORKDIR}/files/${HASH}.gz" ]; then
+ echo "files/${HASH}.gz not found -- snapshot corrupt."
+ return 1
+ fi
+ case ${FILE} in
+ */)
+ rm -rf ${PORTSDIR}/${FILE}
+ mkdir -p ${PORTSDIR}/${FILE}
+ tar -xzf ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR}/${FILE}
+ ;;
+ *)
+ rm -f ${PORTSDIR}/${FILE}
+ tar -xzf ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR} ${FILE}
+ ;;
+ esac
+ done
+ if [ ! -z "${EXTRACTPATH}" ]; then
+ return 0;
+ fi
+
+ extract_metadata
+ extract_indices
+}
+
+# Do the actual work involved in "update"
+update_run() {
+ if ! [ -z "${INDEXONLY}" ]; then
+ extract_indices >/dev/null || return 1
+ return 0
+ fi
+
+ echo -n "Removing old files and directories... "
+ sort ${WORKDIR}/INDEX | comm -23 ${PORTSDIR}/.portsnap.INDEX - |
+ cut -f 1 -d '|' | lam -s "${PORTSDIR}/" - | xargs rm -rf
+ echo "done."
+
+# Install new files
+ echo "Extracting new files:"
+ sort ${WORKDIR}/INDEX | comm -13 ${PORTSDIR}/.portsnap.INDEX - |
+ while read LINE; do
+ FILE=`echo ${LINE} | cut -f 1 -d '|'`
+ HASH=`echo ${LINE} | cut -f 2 -d '|'`
+ echo ${PORTSDIR}/${FILE}
+ if ! [ -r "${WORKDIR}/files/${HASH}.gz" ]; then
+ echo "files/${HASH}.gz not found -- snapshot corrupt."
+ return 1
+ fi
+ case ${FILE} in
+ */)
+ mkdir -p ${PORTSDIR}/${FILE}
+ tar -xzf ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR}/${FILE}
+ ;;
+ *)
+ tar -xzf ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR} ${FILE}
+ ;;
+ esac
+ done
+
+ extract_metadata
+ extract_indices
+}
+
+#### Main functions -- call parameter-handling and core functions
+
+# Using the command line, configuration file, and defaults,
+# set all the parameters which are needed later.
+get_params() {
+ init_params
+ parse_cmdline $@
+ sanity_conffile
+ default_conffile
+ parse_conffile
+ default_params
+}
+
+# Fetch command. Make sure that we're being called
+# interactively, then run fetch_check_params and fetch_run
+cmd_fetch() {
+ if [ ! -t 0 ]; then
+ echo -n "`basename $0` fetch should not "
+ echo "be run non-interactively."
+ echo "Run `basename $0` cron instead."
+ exit 1
+ fi
+ fetch_check_params
+ fetch_run || exit 1
+}
+
+# Cron command. Make sure the parameters are sensible; wait
+# rand(3600) seconds; then fetch updates. While fetching updates,
+# send output to a temporary file; only print that file if the
+# fetching failed.
+cmd_cron() {
+ fetch_check_params
+ sleep `jot -r 1 0 3600`
+
+ TMPFILE=`mktemp /tmp/portsnap.XXXXXX` || exit 1
+ if ! fetch_run >> ${TMPFILE}; then
+ cat ${TMPFILE}
+ rm ${TMPFILE}
+ exit 1
+ fi
+
+ rm ${TMPFILE}
+}
+
+# Extract command. Make sure the parameters are sensible,
+# then extract the ports tree (or part thereof).
+cmd_extract() {
+ extract_check_params
+ extract_run || exit 1
+}
+
+# Update command. Make sure the parameters are sensible,
+# then update the ports tree.
+cmd_update() {
+ update_check_params
+ update_run || exit 1
+}
+
+#### Entry point
+
+# Make sure we find utilities from the base system
+export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
+
+get_params $@
+cmd_${COMMAND}
OpenPOWER on IntegriCloud