-
Notifications
You must be signed in to change notification settings - Fork 0
/
writer.go
143 lines (117 loc) · 3.7 KB
/
writer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package ar
import (
"fmt"
"io"
"os"
"strconv"
"strings"
)
// Writer allows for sequential writing of an ar file by consecutively writing
// each file's header using WriteHeader and body using one or more calls to
// Write. Remember to call Close after finishing the last entry.
type Writer interface {
// WriteHeader writes the file entry header and if necessary the global ar
// header. By setting Header.Size == UnknownSize, the file size will be
// auto-corrected. File names longer than 16 characters will be written
// using BSD file name extension.
WriteHeader(*Header) error
// Write writes the actual file content corresponding the file header that
// was previously written using WriteHeader. An ar file can be written in
// multiple consecutive Write calls.
Write([]byte) (int, error)
// Close ensures that the complete ar content is flushed.
Close() error
}
// NewWriter creates a new ar Writer that supports optional automatic file size
// correction (see Header.Size). If the provided writer is not an *os.File (or
// does not implement Seek, Write and WriteAt), auto-correcting the file size
// requires Writer to buffer the file content in memory.
func NewWriter(w io.Writer) Writer {
f, ok := w.(*os.File)
if ok {
return &fileWriter{f: f}
}
return &defaultWriter{w: w}
}
func writeGlobalHeader(w io.Writer) error {
_, err := w.Write([]byte(GlobalHeader))
return err
}
func writeHeader(w io.Writer, hdr *Header) error {
isExtendedName := hdr.hasExtendedName()
name := hdr.Name
if isExtendedName {
// write BSD style placeholder for extended name
name = bsdExtendedFormatPrefix + strconv.Itoa(
hdr.extendedNameSize())
}
err := packString(w, name, nameFieldSize)
if err != nil {
return fmt.Errorf("write file name: %w", err)
}
err = packUint64(w, hdr.ModTime.Unix(), modTimeFieldSize)
if err != nil {
return fmt.Errorf("write mod time: %w", err)
}
err = packUint64(w, hdr.UID, uidFieldSize)
if err != nil {
return fmt.Errorf("write UID: %w", err)
}
err = packUint64(w, hdr.GID, gidFieldSize)
if err != nil {
return fmt.Errorf("write GID: %w", err)
}
err = packOctal(w, hdr.Mode, modeFiledSize)
if err != nil {
return fmt.Errorf("write file mode: %w", err)
}
// write a valid size placeholder value in auto-correct mode
size := hdr.Size
if size == UnknownSize {
// extended file size will be accounted for in finalizeEntry
size = maxSize
} else if isExtendedName {
size += int64(hdr.extendedNameSize())
}
err = packUint64(w, size, sizeFieldSize)
if err != nil {
return fmt.Errorf("write file size placeholder: %w", err)
}
err = packString(w, HeaderTerminator, len(HeaderTerminator))
if err != nil {
return fmt.Errorf("finishing header: %w", err)
}
if isExtendedName {
// add extended name at the beginning of the content
_, err = w.Write(hdr.extendedNameBytes())
if err != nil {
return fmt.Errorf("write BSD extended file name: %w", err)
}
}
return nil
}
func packUint64(w io.Writer, value int64, fieldWidth int) error {
return packString(w, strconv.FormatInt(value, 10), fieldWidth)
}
func packOctal(w io.Writer, value uint32, fieldWidth int) error {
return packString(w, "100"+strconv.FormatUint(uint64(value), 8), fieldWidth)
}
func packString(w io.Writer, s string, fieldWidth int) error {
data, err := expandToByteField(s, fieldWidth)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
func expandToByteField(s string, fieldWidth int) ([]byte, error) {
switch {
case len(s) < fieldWidth:
return []byte(s + strings.Repeat(" ", fieldWidth-len(s))), nil
case len(s) == fieldWidth:
return []byte(s), nil
default:
return nil, fmt.Errorf("%d byte value %q is too large for %d byte field",
len(s), s, fieldWidth)
}
}