shithub: s3

Download patch

ref: b9cebbe72d920e6bb49e09a80ef60ae1480a4fa7
parent: ff8248458f3c941e86c87ac191cb007453a61fdd
author: Jacob Moody <moody@posixcafe.org>
date: Sat Sep 27 21:57:41 EDT 2025

cat: support partial downloads

--- a/cat.c
+++ b/cat.c
@@ -7,7 +7,7 @@
 _Noreturn void
 usage(void)
 {
-	fprint(2, "Usage %s: s3://bucket/file\n", argv0);
+	fprint(2, "Usage %s: [-o offset] [-n count] s3://bucket/file\n", argv0);
 	exits("usage");
 }
 
@@ -15,22 +15,40 @@
 main(int argc , char **argv)
 {
 	S3 s3;
-	int i;
 	Biobuf *b;
 	char path[512];
+	long offset, n;
+	int dorange;
 
 	tmfmtinstall();
 	fmtinstall('H', encodefmt);
-	i = parseargs(&s3, argc, argv);
-	argc -= i;
-	argv += i;
+	parseargs(&s3, argc, argv);
+	dorange = 0;
+	offset = n = -1;
+	ARGBEGIN{
+	case 'o':
+		dorange++;
+		offset = atoi(EARGF(usage()));
+		break;
+	case 'n':
+		dorange++;
+		n = atoi(EARGF(usage()));
+		break;
+	}ARGEND
 
 	if(argc == 0)
 		usage();
+	if(dorange && (offset == -1 || n == -1)){
+		fprint(2, "if using ranges both -o and -n must be specified\n");
+		exits("usage");
+	}
 	if(parseuri(&s3, path, sizeof path, argv[0]) < 0)
 		usage();
 	b = Bfdopen(1, OWRITE);
-	download(&s3, path, b, s3get);
+	if(dorange)
+		downloadrange(&s3, path, b, offset, n);
+	else
+		download(&s3, path, b, s3get);
 	Bterm(b);
 	exits(nil);
 }
--- a/cmd.c
+++ b/cmd.c
@@ -3,25 +3,42 @@
 #include <bio.h>
 #include "s3.h"
 
-void
-download(S3 *s3, char *path, Biobuf *local, int (*fn)(S3*,Hcon*,char*))
+static void
+dump(Hcon *con, Biobuf *out)
 {
 	long n;
-	Hcon con;
 	char data[8192];
 
-	if(fn(s3, &con, path) < 0)
-		sysfatal("failed to create request: %r");
 	for(;;){
-		n = read(con.body, data, sizeof data);
+		n = read(con->body, data, sizeof data);
 		if(n < 0)
 			sysfatal("download body: %r");
 		if(n == 0){
-			hclose(&con);
+			hclose(con);
 			return;
 		}
-		Bwrite(local, data, n);
+		Bwrite(out, data, n);
 	}
+}
+
+void
+download(S3 *s3, char *path, Biobuf *local, int (*fn)(S3*,Hcon*,char*))
+{
+	Hcon con;
+
+	if(fn(s3, &con, path) < 0)
+		sysfatal("failed to create request: %r");
+	dump(&con, local);
+}
+
+void
+downloadrange(S3 *s3, char *path, Biobuf *local, long off, long n)
+{
+	Hcon con;
+
+	if(s3getrange(s3, &con, path, off, n) < 0)
+		sysfatal("failed to create request: %r");
+	dump(&con, local);
 }
 
 int
--- a/cmd.h
+++ b/cmd.h
@@ -1,3 +1,4 @@
 void download(S3*, char*, Biobuf*, int (*fn)(S3*,Hcon*,char*));
+void downloadrange(S3*, char*, Biobuf*, long, long);
 int parseuri(S3*, char*, int, char*);
 int parseargs(S3*, int, char**);
--- a/s3.c
+++ b/s3.c
@@ -13,6 +13,7 @@
 	char *method;
 	char time[128];
 	char authhdr[512];
+	char *range;
 } Hreq;
 
 static void
@@ -59,6 +60,7 @@
 	hreq->path = path;
 	hreq->payhash = payhash;
 	hreq->mime = mime;
+	hreq->range = nil;
 }
 
 static void
@@ -132,6 +134,8 @@
 		return -1;
 	if(hreq->mime != nil && ctlprint(cfd, "contenttype %s", hreq->mime) < 0)
 		return -1;
+	if(hreq->range != nil && ctlprint(cfd, "headers range: %s", hreq->range) < 0)
+		return -1;
 	return 0;
 }
 
@@ -223,6 +227,19 @@
 	uchar payhash[SHA2_256dlen];
 
 	mkreq(&h, "GET", path, payhash, nil);
+	return hopen(con, s3, OREAD, &h);
+}
+
+int
+s3getrange(S3 *s3, Hcon *con, char *path, long off, long n)
+{
+	Hreq h;
+	uchar payhash[SHA2_256dlen];
+	char buf[64];
+
+	mkreq(&h, "GET", path, payhash, nil);
+	snprint(buf, sizeof buf, "bytes=%ld-%ld", off, off+n-1);
+	h.range = buf;
 	return hopen(con, s3, OREAD, &h);
 }
 
--- a/s3.h
+++ b/s3.h
@@ -20,6 +20,7 @@
 int s3put(S3 *s3, Hcon *con, char *path, char *mime, uchar *payhash);
 int s3post(S3 *s3, Hcon *con, char *path);
 int s3postwrite(S3 *s3, Hcon *con, char *path, char *mime, uchar *payhash);
+int s3getrange(S3 *s3, Hcon *con, char *path, long off, long n);
 
 void hclose(Hcon *con);
 int hdone(Hcon *con);
--