Skip to content

Commit 8e10930

Browse files
authored
s2sx: Limit max executable size (#368)
Work around problem with operating system limits and make executable a max size. Continue to another file.
1 parent ba2263c commit 8e10930

File tree

3 files changed

+109
-6
lines changed

3 files changed

+109
-6
lines changed

s2/README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ Usage: s2sx [options] file1 file2
230230
231231
Compresses all files supplied as input separately.
232232
If files have '.s2' extension they are assumed to be compressed already.
233-
Output files are written as 'filename.s2sfx' and with '.exe' for windows targets.
233+
Output files are written as 'filename.s2sx' and with '.exe' for windows targets.
234+
If output is big, an additional file with ".more" is written. This must be included as well.
234235
By default output files will be overwritten.
235236
236237
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
@@ -244,6 +245,8 @@ Options:
244245
Compress using this amount of threads (default 32)
245246
-help
246247
Display help
248+
-max string
249+
Maximum executable size. Rest will be written to another file. (default "1G")
247250
-os string
248251
Destination operating system (default "windows")
249252
-q Don't write any output to terminal, except errors
@@ -267,6 +270,17 @@ Available platforms are:
267270
* windows-386
268271
* windows-amd64
269272

273+
By default, there is a size limit of 1GB for the output executable.
274+
275+
When this is exceeded the remaining file content is written to a file called
276+
output+`.more`. This file must be included for a successful extraction and
277+
placed alongside the executable for a successful extraction.
278+
279+
This file *must* have the same name as the executable, so if the executable is renamed,
280+
so must the `.more` file.
281+
282+
This functionality is disabled with stdin/stdout.
283+
270284
### Self-extracting TAR files
271285

272286
If you wrap a TAR file you can specify `-untar` to make it untar on the destination host.

s2/cmd/_s2sx/_unpack/main.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ func main() {
5656
exitErr(err)
5757
rd, err := newReader(f, stat.Size())
5858
exitErr(err)
59+
f2, err := os.Open(me + ".more")
60+
if err == nil {
61+
rd = io.MultiReader(rd, f2)
62+
}
63+
if !os.IsNotExist(err) {
64+
exitErr(err)
65+
}
5966
var tmp [1]byte
6067
_, err = io.ReadFull(rd, tmp[:])
6168
exitErr(err)
@@ -69,8 +76,8 @@ func main() {
6976
switch tmp[0] {
7077
case opUnpack:
7178
outname := me + "-extracted"
72-
if idx := strings.Index(me, ".s2sfx"); idx > 0 {
73-
// Trim from '.s2sfx'
79+
if idx := strings.Index(me, ".s2sx"); idx > 0 {
80+
// Trim from '.s2sx'
7481
outname = me[:idx]
7582
}
7683
var out io.Writer

s2/cmd/_s2sx/main.go

+85-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
"path"
1515
"path/filepath"
1616
"runtime"
17+
"strconv"
1718
"strings"
1819
"sync"
1920
"time"
21+
"unicode"
2022

2123
"github.com/klauspost/compress/s2"
2224
"github.com/klauspost/compress/s2/cmd/internal/readahead"
@@ -31,6 +33,7 @@ var (
3133
goos = flag.String("os", runtime.GOOS, "Destination operating system")
3234
goarch = flag.String("arch", runtime.GOARCH, "Destination architecture")
3335
cpu = flag.Int("cpu", runtime.GOMAXPROCS(0), "Compress using this amount of threads")
36+
max = flag.String("max", "1G", "Maximum executable size. Rest will be written to another file.")
3437
safe = flag.Bool("safe", false, "Do not overwrite output files")
3538
stdout = flag.Bool("c", false, "Write all output to stdout. Multiple input files will be concatenated")
3639
remove = flag.Bool("rm", false, "Delete source file(s) after successful compression")
@@ -48,6 +51,8 @@ var embeddedFiles embed.FS
4851
func main() {
4952
flag.Parse()
5053
args := flag.Args()
54+
sz, err := toSize(*max)
55+
exitErr(err)
5156
if len(args) == 0 || *help {
5257
_, _ = fmt.Fprintf(os.Stderr, "s2sx v%v, built at %v.\n\n", version, date)
5358
_, _ = fmt.Fprintf(os.Stderr, "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n"+
@@ -58,7 +63,8 @@ Compresses all files supplied as input separately.
5863
Use file name - to read from stdin and write to stdout.
5964
If input is already s2 compressed it will just have the executable wrapped.
6065
Use s2c commandline tool for advanced option, eg 'cat file.txt||s2c -|s2sx - >out.s2sx'
61-
Output files are written as 'filename.s2sfx' and with '.exe' for windows targets.
66+
Output files are written as 'filename.s2sx' and with '.exe' for windows targets.
67+
If output is big, an additional file with ".more" is written. This must be included as well.
6268
By default output files will be overwritten.
6369
6470
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
@@ -94,6 +100,10 @@ Options:`)
94100
exec, err = ioutil.ReadAll(s2.NewReader(bytes.NewBuffer(exec)))
95101
exitErr(err)
96102

103+
written := int64(0)
104+
if int64(len(exec))+1 >= sz {
105+
exitErr(fmt.Errorf("max size less than unpacker. Max size must be at least %d bytes", len(exec)+1))
106+
}
97107
mode := byte(opUnpack)
98108
if *untar {
99109
mode = opUnTar
@@ -107,6 +117,7 @@ Options:`)
107117
exitErr(err)
108118
_, err = os.Stdout.Write([]byte{mode})
109119
exitErr(err)
120+
written += int64(len(exec) + 1)
110121
}
111122

112123
if stdIn {
@@ -139,7 +150,7 @@ Options:`)
139150
for _, filename := range files {
140151
func() {
141152
var closeOnce sync.Once
142-
dstFilename := fmt.Sprintf("%s%s", strings.TrimPrefix(filename, ".s2"), ".s2sfx")
153+
dstFilename := fmt.Sprintf("%s%s", strings.TrimPrefix(filename, ".s2"), ".s2sx")
143154
if *goos == "windows" {
144155
dstFilename += ".exe"
145156
}
@@ -172,7 +183,23 @@ Options:`)
172183
dstFile, err := os.OpenFile(dstFilename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
173184
exitErr(err)
174185
defer dstFile.Close()
175-
bw := bufio.NewWriterSize(dstFile, 4<<20*2)
186+
sw := &switchWriter{w: dstFile, left: sz, close: nil}
187+
sw.fn = func() {
188+
dstFilename := dstFilename + ".more"
189+
if *safe {
190+
_, err := os.Stat(dstFilename)
191+
if !os.IsNotExist(err) {
192+
exitErr(fmt.Errorf("destination '%s' file exists", dstFilename))
193+
}
194+
}
195+
dstFile, err := os.OpenFile(dstFilename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
196+
exitErr(err)
197+
sw.close = dstFile.Close
198+
sw.w = dstFile
199+
sw.left = (1 << 63) - 1
200+
}
201+
defer sw.Close()
202+
bw := bufio.NewWriterSize(sw, 4<<20*2)
176203
defer bw.Flush()
177204
out = bw
178205
_, err = out.Write(exec)
@@ -252,3 +279,58 @@ func isS2Input(rd io.Reader) (bool, io.Reader) {
252279
exitErr(err)
253280
return false, nil
254281
}
282+
283+
// toSize converts a size indication to bytes.
284+
func toSize(size string) (int64, error) {
285+
size = strings.ToUpper(strings.TrimSpace(size))
286+
firstLetter := strings.IndexFunc(size, unicode.IsLetter)
287+
if firstLetter == -1 {
288+
firstLetter = len(size)
289+
}
290+
291+
bytesString, multiple := size[:firstLetter], size[firstLetter:]
292+
bytes, err := strconv.ParseInt(bytesString, 10, 64)
293+
if err != nil {
294+
return 0, fmt.Errorf("unable to parse size: %v", err)
295+
}
296+
297+
switch multiple {
298+
case "G", "GB", "GIB":
299+
return bytes * 1 << 20, nil
300+
case "M", "MB", "MIB":
301+
return bytes * 1 << 20, nil
302+
case "K", "KB", "KIB":
303+
return bytes * 1 << 10, nil
304+
case "B", "":
305+
return bytes, nil
306+
default:
307+
return 0, fmt.Errorf("unknown size suffix: %v", multiple)
308+
}
309+
}
310+
311+
type switchWriter struct {
312+
w io.Writer
313+
left int64
314+
fn func()
315+
close func() error
316+
}
317+
318+
func (w *switchWriter) Write(b []byte) (int, error) {
319+
if int64(len(b)) <= w.left {
320+
w.left -= int64(len(b))
321+
return w.w.Write(b)
322+
}
323+
n, err := w.w.Write(b[:w.left])
324+
if err != nil {
325+
return n, err
326+
}
327+
w.fn()
328+
n2, err := w.Write(b[n:])
329+
return n + n2, err
330+
}
331+
func (w *switchWriter) Close() error {
332+
if w.close == nil {
333+
return nil
334+
}
335+
return w.close()
336+
}

0 commit comments

Comments
 (0)