https://github.com/robbat2/kup/pull/1 From ee7223a8eea366ae8c39450f25272f3006732abb Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson" Date: Wed, 11 Mar 2026 21:39:06 -0700 Subject: [PATCH] feat: putraw command Signed-off-by: Robin H. Johnson --- kup | 46 ++++++++++++++++++++++++++++++++++++ kup-server | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ kup-server.1 | 9 ++++++- kup.1 | 46 +++++++++++++++++++++++++++++------- 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/kup b/kup index f3a5d0f..bdb37c5 100755 --- a/kup +++ b/kup @@ -93,6 +93,7 @@ sub usage($) { print STDERR " put local_file signature remote_path\n"; print STDERR " put --tar [--prefix=] remote_tree ref signature remote_path\n"; print STDERR " put --diff remote_tree ref1 ref2 signature remote_path\n"; + print STDERR " putraw local_file signature remote_path\n"; print STDERR " mkdir remote_path\n"; print STDERR " mv|move old_path new_path\n"; print STDERR " ln|link old_path new_path\n"; @@ -474,6 +475,49 @@ sub cmd_put() command('PUT', url_encode($remote)); } +# PUTRAW command - upload a file exactly as-is without recompression +sub cmd_putraw() +{ + my $file = shift @args; + + if ($file =~ /^-/) { + die "$0: unknown option to putraw command: $file\n"; + } + + # Upload the file as-is; force plain ('%') format so the server stores + # exactly the bytes we have locally without decompressing. + cat_file('DATA', $file, '%'); + + # Get the local filename without directory + my($vol, $dir, $file_tail); + ($vol, $dir, $file_tail) = File::Spec->splitpath($file); + + my $sign = shift @args; + my $remote = shift @args; + + if (!defined($remote)) { + usage(1); + } + + # Allow trailing slash to use local filename + if ($remote =~ m:/$: && defined($file_tail)) { + $remote .= $file_tail; + } + + my $xrt = $remote; + $remote = canonicalize_path($remote); + if (!is_valid_filename($remote)) { + die "$0: invalid pathname: $xrt\n"; + } + + if ($remote =~ /\.sign$/) { + die "$0: target filename cannot end in .sign\n"; + } + + cat_file('SIGN', $sign, undef); + command('PUTRAW', url_encode($remote)); +} + # MKDIR command sub cmd_mkdir() { @@ -601,6 +645,8 @@ sub process_commands() if ($cmd eq 'put') { cmd_put(); + } elsif ($cmd eq 'putraw') { + cmd_putraw(); } elsif ($cmd eq 'mkdir') { cmd_mkdir(); } elsif ($cmd eq 'move' || $cmd eq 'mv') { diff --git a/kup-server b/kup-server index 8bdab50..ba326fa 100755 --- a/kup-server +++ b/kup-server @@ -30,6 +30,8 @@ # - updates the current signature blob (follows immediately) # PUT pathname # - installs the current data blob as +# PUTRAW pathname +# - installs the current data blob as without recompression # MKDIR pathname # - creates a new directory # MOVE old-path new-path @@ -903,6 +905,69 @@ sub put_file(@) cleanup(); } +sub putraw_file(@) +{ + my @args = @_; + + if (scalar(@args) != 1) { + fatal("Bad PUTRAW command"); + } + + my($file) = @args; + + if (!$have_data) { + fatal("PUTRAW without DATA"); + } + if (!$have_sign) { + fatal("PUTRAW without SIGN"); + } + + if (!signature_valid()) { + fatal("Signature invalid"); + } + + if (!is_valid_filename($file)) { + fatal("Invalid filename in PUTRAW command"); + } + + if ($file =~ /\.sign$/) { + fatal("$file: Target filename cannot end in .sign"); + } + + make_timestamps_match(); + + # Log SHA256 of the raw (as-uploaded) file + my $sha = Digest::SHA->new('sha256'); + print STDERR "\rCalculating sha256 for ".$file." "; + $sha->addfile($tmpdir.'/data'); + syslog(LOG_NOTICE, "sha256: %s: %s", $file, $sha->hexdigest); + print STDERR "... logged.\n"; + + lock_tree(); + + foreach my $e ('', '.sign') { + if (-e $data_path.$file.$e && ! -f _) { + fatal("$file: Trying to overwrite a non-file"); + } + } + + my @install_ext = ('.sign', ''); + my @undoes = (); + foreach my $e (@install_ext) { + my $target = $data_path.$file.$e; + if (!rename($tmpdir.'/data'.$e, $target)) { + my $err = $!; + unlink(@undoes); + $! = $err; + fatal("$file: Failed to install files: $!"); + } + push(@undoes, $target); + } + + unlock_tree(); + cleanup(); +} + sub do_mkdir(@) { my @args = @_; @@ -1305,6 +1370,8 @@ while (defined($line = get_command())) { get_sign_data(@args); } elsif ($cmd eq 'PUT') { put_file(@args); + } elsif ($cmd eq 'PUTRAW') { + putraw_file(@args); } elsif ($cmd eq 'MKDIR') { do_mkdir(@args); } elsif ($cmd eq 'MOVE' || $cmd eq 'LINK') { diff --git a/kup-server.1 b/kup-server.1 index 2143090..6dd8ec7 100644 --- a/kup-server.1 +++ b/kup-server.1 @@ -28,6 +28,12 @@ for specific tree access control. On the client side, a corresponding client-side utility .BR kup is used to initiate the connection and perform the uploads. +.PP +Uploaded files must be accompanied by a PGP detached signature. For +the \fBPUT\fP command the signature covers the uncompressed content and +the server generates all configured compression formats. For the +\fBPUTRAW\fP command the signature covers the file exactly as uploaded, +and the server stores it verbatim without recompression. .SH GLOBAL CONFIG .PP The configuration file for @@ -127,4 +133,5 @@ or (at your option) any later version; incorporated herein by reference. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" -.BR kup (1) +.BR kup (1), +.BR kup-proto (5) diff --git a/kup.1 b/kup.1 index 811afb3..6ad8210 100644 --- a/kup.1 +++ b/kup.1 @@ -18,9 +18,13 @@ kup \- kernel.org upload utility .PP This utility is used to upload files to \fIkernel.org\fP and other systems using the same upload system (\fBkup-server\fP). Each upload -is required to have a PGP signature, and the server will generate -multiple compressed formats if the content uploaded is intended to be -compressed. +is required to have a PGP signature. For the +.B put +command, the server will generate multiple compressed formats if the +content uploaded is intended to be compressed. For the +.B putraw +command, the file is stored exactly as uploaded without any +recompression. .PP Additionally, if the user has content from a .BR git (1) @@ -68,15 +72,19 @@ or if not set, no subcommand will be used (default kup-server behavior). A series of commands can be specified on a single command line, separated by a double dash argument (\fB\-\-\fP). .PP -In all cases, PGP signatures are detached signature files +For the \fBput\fP command, PGP signatures are detached signature files corresponding to the \fIuncompressed\fP content. If a -\fIremote_path\fP ends in \fP\.gz\fP then +\fIremote_path\fP ends in \fB\.gz\fP then .BR gzip , .B bzip2 and .B xz compressed files are generated on the server; otherwise the content is stored uncompressed. +.PP +For the \fBputraw\fP command, the PGP signature must correspond to the +exact bytes of \fIlocal_file\fP as uploaded. The file is stored +verbatim at \fIremote_path\fP with no recompression. .TP \fBput\fP \fIlocal_file\fP \fPsignature_file\fP \fIremote_path\fP Upload the file \fIlocal_file\fP signed with @@ -111,6 +119,14 @@ version of .B git locally as on the server in order to produce a valid signature. .TP +\fBputraw\fP \fIlocal_file\fP \fIsignature_file\fP \fIremote_path\fP +Upload the file \fIlocal_file\fP signed with \fIsignature_file\fP and +store it at \fIremote_path\fP exactly as-is, without any +decompression or recompression. The signature must cover the exact +bytes of \fIlocal_file\fP. Unlike \fBput\fP, the remote filename +extension is not remapped and no additional compression formats are +generated. +.TP \fBmkdir\fP \fIremote_path\fP Create a new directory on the server. .TP @@ -139,10 +155,10 @@ relative to the \fIold_path\fP minus the final component. Similarly, if \fInew_path\fP ends in a slash then the final component of \fIold_path\fP will be appended. .PP -For the \fPput\fP command, except when \fB\-\-tar\fP or \fB\-\-diff\fP -is specified, if the \fIremote_path\fP ends in a slash then the -final (filename) component of \fIlocal_file\fP will be appended to the -final pathname. +For the \fBput\fP command, except when \fB\-\-tar\fP or \fB\-\-diff\fP +is specified, and for the \fBputraw\fP command, if the \fIremote_path\fP +ends in a slash then the final (filename) component of \fIlocal_file\fP +will be appended to the final pathname. .SH CONFIG FILE Kup checks the presence of $HOME/.kuprc and can load the .B host @@ -174,6 +190,16 @@ kup put foolib-1.0.tar.bz2 foolib-1.0.tar.asc /pub/foolib/foolib-1.0.tar.bz2 .fi .RE .PP +Upload a pre-built tarball exactly as-is (e.g. a release artifact that +must not be altered), signing the compressed file directly: +.PP +.RS +.nf +gpg --detach-sign --armor foolib-1.0.tar.gz +kup putraw foolib-1.0.tar.gz foolib-1.0.tar.gz.asc /pub/foolib/foolib-1.0.tar.gz +.fi +.RE +.PP Generate a tarball locally, sign it, then tell kup-server to generate an identical tarball on the server, verify the signature, and put the compressed results in /pub/foolib: @@ -197,6 +223,8 @@ or (at your option) any later version; incorporated herein by reference. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" +.BR kup-proto (5), +.BR kup-server (1), .BR git (1), .BR ssh (1), .BR gzip (1),