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);
--
⑨