github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/cpio/newc.go (about) 1 // Copyright 2013-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cpio 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "encoding/hex" 11 "fmt" 12 "io" 13 "os" 14 15 "github.com/mvdan/u-root-coreutils/pkg/uio" 16 ) 17 18 const ( 19 newcMagic = "070701" 20 magicLen = 6 21 ) 22 23 // Newc is the newc CPIO record format. 24 var Newc RecordFormat = newc{magic: newcMagic} 25 26 type header struct { 27 Ino uint32 28 Mode uint32 29 UID uint32 30 GID uint32 31 NLink uint32 32 MTime uint32 33 FileSize uint32 34 Major uint32 35 Minor uint32 36 Rmajor uint32 37 Rminor uint32 38 NameLength uint32 39 CRC uint32 40 } 41 42 func headerFromInfo(i Info) header { 43 var h header 44 h.Ino = uint32(i.Ino) 45 h.Mode = uint32(i.Mode) 46 h.UID = uint32(i.UID) 47 h.GID = uint32(i.GID) 48 h.NLink = uint32(i.NLink) 49 h.MTime = uint32(i.MTime) 50 h.FileSize = uint32(i.FileSize) 51 h.Major = uint32(i.Major) 52 h.Minor = uint32(i.Minor) 53 h.Rmajor = uint32(i.Rmajor) 54 h.Rminor = uint32(i.Rminor) 55 h.NameLength = uint32(len(i.Name)) + 1 56 return h 57 } 58 59 func (h header) Info() Info { 60 var i Info 61 i.Ino = uint64(h.Ino) 62 i.Mode = uint64(h.Mode) 63 i.UID = uint64(h.UID) 64 i.GID = uint64(h.GID) 65 i.NLink = uint64(h.NLink) 66 i.MTime = uint64(h.MTime) 67 i.FileSize = uint64(h.FileSize) 68 i.Major = uint64(h.Major) 69 i.Minor = uint64(h.Minor) 70 i.Rmajor = uint64(h.Rmajor) 71 i.Rminor = uint64(h.Rminor) 72 return i 73 } 74 75 // newc implements RecordFormat for the newc format. 76 type newc struct { 77 magic string 78 } 79 80 // round4 returns the next multiple of 4 close to n. 81 func round4(n int64) int64 { 82 return (n + 3) &^ 0x3 83 } 84 85 type writer struct { 86 n newc 87 w io.Writer 88 pos int64 89 } 90 91 // Writer implements RecordFormat.Writer. 92 func (n newc) Writer(w io.Writer) RecordWriter { 93 return NewDedupWriter(&writer{n: n, w: w}) 94 } 95 96 func (w *writer) Write(b []byte) (int, error) { 97 n, err := w.w.Write(b) 98 if err != nil { 99 return 0, err 100 } 101 w.pos += int64(n) 102 return n, nil 103 } 104 105 func (w *writer) pad() error { 106 if o := round4(w.pos); o != w.pos { 107 var pad [3]byte 108 if _, err := w.Write(pad[:o-w.pos]); err != nil { 109 return err 110 } 111 } 112 return nil 113 } 114 115 // WriteRecord writes newc cpio records. It pads the header+name write to 4 116 // byte alignment and pads the data write as well. 117 func (w *writer) WriteRecord(f Record) error { 118 // Write magic. 119 if _, err := w.Write([]byte(w.n.magic)); err != nil { 120 return err 121 } 122 123 buf := &bytes.Buffer{} 124 hdr := headerFromInfo(f.Info) 125 if f.ReaderAt == nil { 126 hdr.FileSize = 0 127 } 128 hdr.CRC = 0 129 if err := binary.Write(buf, binary.BigEndian, hdr); err != nil { 130 return err 131 } 132 133 hexBuf := make([]byte, hex.EncodedLen(buf.Len())) 134 n := hex.Encode(hexBuf, buf.Bytes()) 135 // It's much easier to debug if we match GNU output format. 136 hexBuf = bytes.ToUpper(hexBuf) 137 138 // Write header. 139 if _, err := w.Write(hexBuf[:n]); err != nil { 140 return err 141 } 142 143 // Append NULL char. 144 cstr := append([]byte(f.Info.Name), 0) 145 // Write name. 146 if _, err := w.Write(cstr); err != nil { 147 return err 148 } 149 150 // Pad to a multiple of 4. 151 if err := w.pad(); err != nil { 152 return err 153 } 154 155 // Some files do not have any content. 156 if f.ReaderAt == nil { 157 return nil 158 } 159 160 // Write file contents. 161 m, err := io.Copy(w, uio.Reader(f)) 162 if err != nil { 163 return err 164 } 165 if m != int64(f.Info.FileSize) { 166 return fmt.Errorf("WriteRecord: %s: wrote %d bytes of file instead of %d bytes; archive is now corrupt", f.Info.Name, m, f.Info.FileSize) 167 } 168 if c, ok := f.ReaderAt.(io.Closer); ok { 169 if err := c.Close(); err != nil { 170 return err 171 } 172 } 173 if m > 0 { 174 return w.pad() 175 } 176 return nil 177 } 178 179 type reader struct { 180 n newc 181 r io.ReaderAt 182 pos int64 183 } 184 185 // discarder is used to implement ReadAt from a Reader 186 // by reading, and discarding, data until the offset 187 // is reached. It can only go forward. It is designed 188 // for pipe-like files. 189 type discarder struct { 190 r io.Reader 191 pos int64 192 } 193 194 // ReadAt implements ReadAt for a discarder. 195 // It is an error for the offset to be negative. 196 func (r *discarder) ReadAt(p []byte, off int64) (int, error) { 197 if off-r.pos < 0 { 198 return 0, fmt.Errorf("negative seek on discarder not allowed") 199 } 200 if off != r.pos { 201 i, err := io.Copy(io.Discard, io.LimitReader(r.r, off-r.pos)) 202 if err != nil || i != off-r.pos { 203 return 0, err 204 } 205 r.pos += i 206 } 207 n, err := io.ReadFull(r.r, p) 208 if err != nil { 209 return n, err 210 } 211 r.pos += int64(n) 212 return n, err 213 } 214 215 var _ io.ReaderAt = &discarder{} 216 217 // Reader implements RecordFormat.Reader. 218 func (n newc) Reader(r io.ReaderAt) RecordReader { 219 return EOFReader{&reader{n: n, r: r}} 220 } 221 222 // NewFileReader implements RecordFormat.Reader. If the file 223 // implements ReadAt, then it is used for greater efficiency. 224 // If it only implements Read, then a discarder will be used 225 // instead. 226 // Note a complication: 227 // 228 // r, _, _ := os.Pipe() 229 // var b [2]byte 230 // _, err := r.ReadAt(b[:], 0) 231 // fmt.Printf("%v", err) 232 // 233 // Pipes claim to implement ReadAt; most Unix kernels 234 // do not agree. Even a seek to the current position fails. 235 // This means that 236 // if rat, ok := r.(io.ReaderAt); ok { 237 // would seem to work, but would fail when the 238 // actual ReadAt on the pipe occurs, even for offset 0, 239 // which does not require a seek! The kernel checks for 240 // whether the fd is seekable and returns an error, 241 // even for values of offset which won't require a seek. 242 // So, the code makes a simple test: can we seek to 243 // current offset? If not, then the file is wrapped with a 244 // discardreader. The discard reader is far less efficient 245 // but allows cpio to read from a pipe. 246 func (n newc) NewFileReader(f *os.File) (RecordReader, error) { 247 _, err := f.Seek(0, 0) 248 if err == nil { 249 return EOFReader{&reader{n: n, r: f}}, nil 250 } 251 return EOFReader{&reader{n: n, r: &discarder{r: f}}}, nil 252 } 253 254 func (r *reader) read(p []byte) error { 255 n, err := r.r.ReadAt(p, r.pos) 256 257 if err == io.EOF { 258 return io.EOF 259 } 260 261 if err != nil || n != len(p) { 262 return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %v", r.pos, n, len(p), err) 263 } 264 265 r.pos += int64(n) 266 return nil 267 } 268 269 func (r *reader) readAligned(p []byte) error { 270 err := r.read(p) 271 r.pos = round4(r.pos) 272 return err 273 } 274 275 // ReadRecord implements RecordReader for the newc cpio format. 276 func (r *reader) ReadRecord() (Record, error) { 277 hdr := header{} 278 recPos := r.pos 279 280 buf := make([]byte, hex.EncodedLen(binary.Size(hdr))+magicLen) 281 if err := r.read(buf); err != nil { 282 return Record{}, err 283 } 284 285 // Check the magic. 286 if magic := string(buf[:magicLen]); magic != r.n.magic { 287 return Record{}, fmt.Errorf("reader: magic got %q, want %q", magic, r.n.magic) 288 } 289 290 // Decode hex header fields. 291 dst := make([]byte, binary.Size(hdr)) 292 if _, err := hex.Decode(dst, buf[magicLen:]); err != nil { 293 return Record{}, fmt.Errorf("reader: error decoding hex: %v", err) 294 } 295 if err := binary.Read(bytes.NewReader(dst), binary.BigEndian, &hdr); err != nil { 296 return Record{}, err 297 } 298 Debug("Decoded header is %v\n", hdr) 299 300 // Get the name. 301 if hdr.NameLength == 0 { 302 return Record{}, fmt.Errorf("name field of length zero") 303 } 304 nameBuf := make([]byte, hdr.NameLength) 305 if err := r.readAligned(nameBuf); err != nil { 306 Debug("name read failed") 307 return Record{}, err 308 } 309 310 info := hdr.Info() 311 info.Name = Normalize(string(nameBuf[:hdr.NameLength-1])) 312 313 recLen := uint64(r.pos - recPos) 314 filePos := r.pos 315 316 //TODO: check if hdr.FileSize is equal to the actual fileSize of the record 317 content := io.NewSectionReader(r.r, r.pos, int64(hdr.FileSize)) 318 r.pos = round4(r.pos + int64(hdr.FileSize)) 319 return Record{ 320 Info: info, 321 ReaderAt: content, 322 RecLen: recLen, 323 RecPos: recPos, 324 FilePos: filePos, 325 }, nil 326 } 327 328 func init() { 329 formatMap["newc"] = Newc 330 }