Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -1,6 +1,6 @@ |
| 1 | 1 |
# the following lines are in sorted order, FYI |
| 2 | 2 |
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 |
| 3 |
-github.com/Microsoft/hcsshim v0.7.6 |
|
| 3 |
+github.com/Microsoft/hcsshim v0.7.9 |
|
| 4 | 4 |
github.com/Microsoft/go-winio v0.4.11 |
| 5 | 5 |
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a |
| 6 | 6 |
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git |
| 7 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1263 @@ |
| 0 |
+package compactext4 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "encoding/binary" |
|
| 6 |
+ "errors" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 9 |
+ "path" |
|
| 10 |
+ "sort" |
|
| 11 |
+ "strings" |
|
| 12 |
+ "time" |
|
| 13 |
+ |
|
| 14 |
+ "github.com/Microsoft/hcsshim/ext4/internal/format" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+// Writer writes a compact ext4 file system. |
|
| 18 |
+type Writer struct {
|
|
| 19 |
+ f io.ReadWriteSeeker |
|
| 20 |
+ bw *bufio.Writer |
|
| 21 |
+ inodes []*inode |
|
| 22 |
+ curName string |
|
| 23 |
+ curInode *inode |
|
| 24 |
+ pos int64 |
|
| 25 |
+ dataWritten, dataMax int64 |
|
| 26 |
+ err error |
|
| 27 |
+ initialized bool |
|
| 28 |
+ supportInlineData bool |
|
| 29 |
+ maxDiskSize int64 |
|
| 30 |
+ gdBlocks uint32 |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// Mode flags for Linux files. |
|
| 34 |
+const ( |
|
| 35 |
+ S_IXOTH = format.S_IXOTH |
|
| 36 |
+ S_IWOTH = format.S_IWOTH |
|
| 37 |
+ S_IROTH = format.S_IROTH |
|
| 38 |
+ S_IXGRP = format.S_IXGRP |
|
| 39 |
+ S_IWGRP = format.S_IWGRP |
|
| 40 |
+ S_IRGRP = format.S_IRGRP |
|
| 41 |
+ S_IXUSR = format.S_IXUSR |
|
| 42 |
+ S_IWUSR = format.S_IWUSR |
|
| 43 |
+ S_IRUSR = format.S_IRUSR |
|
| 44 |
+ S_ISVTX = format.S_ISVTX |
|
| 45 |
+ S_ISGID = format.S_ISGID |
|
| 46 |
+ S_ISUID = format.S_ISUID |
|
| 47 |
+ S_IFIFO = format.S_IFIFO |
|
| 48 |
+ S_IFCHR = format.S_IFCHR |
|
| 49 |
+ S_IFDIR = format.S_IFDIR |
|
| 50 |
+ S_IFBLK = format.S_IFBLK |
|
| 51 |
+ S_IFREG = format.S_IFREG |
|
| 52 |
+ S_IFLNK = format.S_IFLNK |
|
| 53 |
+ S_IFSOCK = format.S_IFSOCK |
|
| 54 |
+ |
|
| 55 |
+ TypeMask = format.TypeMask |
|
| 56 |
+) |
|
| 57 |
+ |
|
| 58 |
+type inode struct {
|
|
| 59 |
+ Size int64 |
|
| 60 |
+ Atime, Ctime, Mtime, Crtime uint64 |
|
| 61 |
+ Number format.InodeNumber |
|
| 62 |
+ Mode uint16 |
|
| 63 |
+ Uid, Gid uint32 |
|
| 64 |
+ LinkCount uint32 |
|
| 65 |
+ XattrBlock uint32 |
|
| 66 |
+ BlockCount uint32 |
|
| 67 |
+ Devmajor, Devminor uint32 |
|
| 68 |
+ Flags format.InodeFlag |
|
| 69 |
+ Data []byte |
|
| 70 |
+ XattrInline []byte |
|
| 71 |
+ Children directory |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+func (node *inode) FileType() uint16 {
|
|
| 75 |
+ return node.Mode & format.TypeMask |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func (node *inode) IsDir() bool {
|
|
| 79 |
+ return node.FileType() == S_IFDIR |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+// A File represents a file to be added to an ext4 file system. |
|
| 83 |
+type File struct {
|
|
| 84 |
+ Linkname string |
|
| 85 |
+ Size int64 |
|
| 86 |
+ Mode uint16 |
|
| 87 |
+ Uid, Gid uint32 |
|
| 88 |
+ Atime, Ctime, Mtime, Crtime time.Time |
|
| 89 |
+ Devmajor, Devminor uint32 |
|
| 90 |
+ Xattrs map[string][]byte |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+const ( |
|
| 94 |
+ inodeFirst = 11 |
|
| 95 |
+ inodeLostAndFound = inodeFirst |
|
| 96 |
+ |
|
| 97 |
+ blockSize = 4096 |
|
| 98 |
+ blocksPerGroup = blockSize * 8 |
|
| 99 |
+ inodeSize = 256 |
|
| 100 |
+ maxInodesPerGroup = blockSize * 8 // Limited by the inode bitmap |
|
| 101 |
+ inodesPerGroupIncrement = blockSize / inodeSize |
|
| 102 |
+ |
|
| 103 |
+ defaultMaxDiskSize = 16 * 1024 * 1024 * 1024 // 16GB |
|
| 104 |
+ maxMaxDiskSize = 16 * 1024 * 1024 * 1024 * 1024 // 16TB |
|
| 105 |
+ |
|
| 106 |
+ groupDescriptorSize = 32 // Use the small group descriptor |
|
| 107 |
+ groupsPerDescriptorBlock = blockSize / groupDescriptorSize |
|
| 108 |
+ |
|
| 109 |
+ maxFileSize = 128 * 1024 * 1024 * 1024 // 128GB file size maximum for now |
|
| 110 |
+ smallSymlinkSize = 59 // max symlink size that goes directly in the inode |
|
| 111 |
+ maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent |
|
| 112 |
+ inodeDataSize = 60 |
|
| 113 |
+ inodeUsedSize = 152 // fields through CrtimeExtra |
|
| 114 |
+ inodeExtraSize = inodeSize - inodeUsedSize |
|
| 115 |
+ xattrInodeOverhead = 4 + 4 // magic number + empty next entry value |
|
| 116 |
+ xattrBlockOverhead = 32 + 4 // header + empty next entry value |
|
| 117 |
+ inlineDataXattrOverhead = xattrInodeOverhead + 16 + 4 // entry + "data" |
|
| 118 |
+ inlineDataSize = inodeDataSize + inodeExtraSize - inlineDataXattrOverhead |
|
| 119 |
+) |
|
| 120 |
+ |
|
| 121 |
+type exceededMaxSizeError struct {
|
|
| 122 |
+ Size int64 |
|
| 123 |
+} |
|
| 124 |
+ |
|
| 125 |
+func (err exceededMaxSizeError) Error() string {
|
|
| 126 |
+ return fmt.Sprintf("disk exceeded maximum size of %d bytes", err.Size)
|
|
| 127 |
+} |
|
| 128 |
+ |
|
| 129 |
+var directoryEntrySize = binary.Size(format.DirectoryEntry{})
|
|
| 130 |
+var extraIsize = uint16(inodeUsedSize - 128) |
|
| 131 |
+ |
|
| 132 |
+type directory map[string]*inode |
|
| 133 |
+ |
|
| 134 |
+func splitFirst(p string) (string, string) {
|
|
| 135 |
+ n := strings.IndexByte(p, '/') |
|
| 136 |
+ if n >= 0 {
|
|
| 137 |
+ return p[:n], p[n+1:] |
|
| 138 |
+ } |
|
| 139 |
+ return p, "" |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func (w *Writer) findPath(root *inode, p string) *inode {
|
|
| 143 |
+ inode := root |
|
| 144 |
+ for inode != nil && len(p) != 0 {
|
|
| 145 |
+ name, rest := splitFirst(p) |
|
| 146 |
+ p = rest |
|
| 147 |
+ inode = inode.Children[name] |
|
| 148 |
+ } |
|
| 149 |
+ return inode |
|
| 150 |
+} |
|
| 151 |
+ |
|
| 152 |
+func timeToFsTime(t time.Time) uint64 {
|
|
| 153 |
+ if t.IsZero() {
|
|
| 154 |
+ return 0 |
|
| 155 |
+ } |
|
| 156 |
+ s := t.Unix() |
|
| 157 |
+ if s < -0x80000000 {
|
|
| 158 |
+ return 0x80000000 |
|
| 159 |
+ } |
|
| 160 |
+ if s > 0x37fffffff {
|
|
| 161 |
+ return 0x37fffffff |
|
| 162 |
+ } |
|
| 163 |
+ return uint64(s) | uint64(t.Nanosecond())<<34 |
|
| 164 |
+} |
|
| 165 |
+ |
|
| 166 |
+func fsTimeToTime(t uint64) time.Time {
|
|
| 167 |
+ if t == 0 {
|
|
| 168 |
+ return time.Time{}
|
|
| 169 |
+ } |
|
| 170 |
+ s := int64(t & 0x3ffffffff) |
|
| 171 |
+ if s > 0x7fffffff && s < 0x100000000 {
|
|
| 172 |
+ s = int64(int32(uint32(s))) |
|
| 173 |
+ } |
|
| 174 |
+ return time.Unix(s, int64(t>>34)) |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+func (w *Writer) getInode(i format.InodeNumber) *inode {
|
|
| 178 |
+ if i == 0 || int(i) > len(w.inodes) {
|
|
| 179 |
+ return nil |
|
| 180 |
+ } |
|
| 181 |
+ return w.inodes[i-1] |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+var xattrPrefixes = []struct {
|
|
| 185 |
+ Index uint8 |
|
| 186 |
+ Prefix string |
|
| 187 |
+}{
|
|
| 188 |
+ {2, "system.posix_acl_access"},
|
|
| 189 |
+ {3, "system.posix_acl_default"},
|
|
| 190 |
+ {8, "system.richacl"},
|
|
| 191 |
+ {7, "system."},
|
|
| 192 |
+ {1, "user."},
|
|
| 193 |
+ {4, "trusted."},
|
|
| 194 |
+ {6, "security."},
|
|
| 195 |
+} |
|
| 196 |
+ |
|
| 197 |
+func compressXattrName(name string) (uint8, string) {
|
|
| 198 |
+ for _, p := range xattrPrefixes {
|
|
| 199 |
+ if strings.HasPrefix(name, p.Prefix) {
|
|
| 200 |
+ return p.Index, name[len(p.Prefix):] |
|
| 201 |
+ } |
|
| 202 |
+ } |
|
| 203 |
+ return 0, name |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+func decompressXattrName(index uint8, name string) string {
|
|
| 207 |
+ for _, p := range xattrPrefixes {
|
|
| 208 |
+ if index == p.Index {
|
|
| 209 |
+ return p.Prefix + name |
|
| 210 |
+ } |
|
| 211 |
+ } |
|
| 212 |
+ return name |
|
| 213 |
+} |
|
| 214 |
+ |
|
| 215 |
+func hashXattrEntry(name string, value []byte) uint32 {
|
|
| 216 |
+ var hash uint32 |
|
| 217 |
+ for i := 0; i < len(name); i++ {
|
|
| 218 |
+ hash = (hash << 5) ^ (hash >> 27) ^ uint32(name[i]) |
|
| 219 |
+ } |
|
| 220 |
+ |
|
| 221 |
+ for i := 0; i+3 < len(value); i += 4 {
|
|
| 222 |
+ hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(value[i:i+4]) |
|
| 223 |
+ } |
|
| 224 |
+ |
|
| 225 |
+ if len(value)%4 != 0 {
|
|
| 226 |
+ var last [4]byte |
|
| 227 |
+ copy(last[:], value[len(value)&^3:]) |
|
| 228 |
+ hash = (hash << 16) ^ (hash >> 16) ^ binary.LittleEndian.Uint32(last[:]) |
|
| 229 |
+ } |
|
| 230 |
+ return hash |
|
| 231 |
+} |
|
| 232 |
+ |
|
| 233 |
+type xattr struct {
|
|
| 234 |
+ Name string |
|
| 235 |
+ Index uint8 |
|
| 236 |
+ Value []byte |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+func (x *xattr) EntryLen() int {
|
|
| 240 |
+ return (len(x.Name)+3)&^3 + 16 |
|
| 241 |
+} |
|
| 242 |
+ |
|
| 243 |
+func (x *xattr) ValueLen() int {
|
|
| 244 |
+ return (len(x.Value) + 3) &^ 3 |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 247 |
+type xattrState struct {
|
|
| 248 |
+ inode, block []xattr |
|
| 249 |
+ inodeLeft, blockLeft int |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+func (s *xattrState) init() {
|
|
| 253 |
+ s.inodeLeft = inodeExtraSize - xattrInodeOverhead |
|
| 254 |
+ s.blockLeft = blockSize - xattrBlockOverhead |
|
| 255 |
+} |
|
| 256 |
+ |
|
| 257 |
+func (s *xattrState) addXattr(name string, value []byte) bool {
|
|
| 258 |
+ index, name := compressXattrName(name) |
|
| 259 |
+ x := xattr{
|
|
| 260 |
+ Index: index, |
|
| 261 |
+ Name: name, |
|
| 262 |
+ Value: value, |
|
| 263 |
+ } |
|
| 264 |
+ length := x.EntryLen() + x.ValueLen() |
|
| 265 |
+ if s.inodeLeft >= length {
|
|
| 266 |
+ s.inode = append(s.inode, x) |
|
| 267 |
+ s.inodeLeft -= length |
|
| 268 |
+ } else if s.blockLeft >= length {
|
|
| 269 |
+ s.block = append(s.block, x) |
|
| 270 |
+ s.blockLeft -= length |
|
| 271 |
+ } else {
|
|
| 272 |
+ return false |
|
| 273 |
+ } |
|
| 274 |
+ return true |
|
| 275 |
+} |
|
| 276 |
+ |
|
| 277 |
+func putXattrs(xattrs []xattr, b []byte, offsetDelta uint16) {
|
|
| 278 |
+ offset := uint16(len(b)) + offsetDelta |
|
| 279 |
+ eb := b |
|
| 280 |
+ db := b |
|
| 281 |
+ for _, xattr := range xattrs {
|
|
| 282 |
+ vl := xattr.ValueLen() |
|
| 283 |
+ offset -= uint16(vl) |
|
| 284 |
+ eb[0] = uint8(len(xattr.Name)) |
|
| 285 |
+ eb[1] = xattr.Index |
|
| 286 |
+ binary.LittleEndian.PutUint16(eb[2:], offset) |
|
| 287 |
+ binary.LittleEndian.PutUint32(eb[8:], uint32(len(xattr.Value))) |
|
| 288 |
+ binary.LittleEndian.PutUint32(eb[12:], hashXattrEntry(xattr.Name, xattr.Value)) |
|
| 289 |
+ copy(eb[16:], xattr.Name) |
|
| 290 |
+ eb = eb[xattr.EntryLen():] |
|
| 291 |
+ copy(db[len(db)-vl:], xattr.Value) |
|
| 292 |
+ db = db[:len(db)-vl] |
|
| 293 |
+ } |
|
| 294 |
+} |
|
| 295 |
+ |
|
| 296 |
+func getXattrs(b []byte, xattrs map[string][]byte, offsetDelta uint16) {
|
|
| 297 |
+ eb := b |
|
| 298 |
+ for len(eb) != 0 {
|
|
| 299 |
+ nameLen := eb[0] |
|
| 300 |
+ if nameLen == 0 {
|
|
| 301 |
+ break |
|
| 302 |
+ } |
|
| 303 |
+ index := eb[1] |
|
| 304 |
+ offset := binary.LittleEndian.Uint16(eb[2:]) - offsetDelta |
|
| 305 |
+ valueLen := binary.LittleEndian.Uint32(eb[8:]) |
|
| 306 |
+ attr := xattr{
|
|
| 307 |
+ Index: index, |
|
| 308 |
+ Name: string(eb[16 : 16+nameLen]), |
|
| 309 |
+ Value: b[offset : uint32(offset)+valueLen], |
|
| 310 |
+ } |
|
| 311 |
+ xattrs[decompressXattrName(index, attr.Name)] = attr.Value |
|
| 312 |
+ eb = eb[attr.EntryLen():] |
|
| 313 |
+ } |
|
| 314 |
+} |
|
| 315 |
+ |
|
| 316 |
+func (w *Writer) writeXattrs(inode *inode, state *xattrState) error {
|
|
| 317 |
+ // Write the inline attributes. |
|
| 318 |
+ if len(state.inode) != 0 {
|
|
| 319 |
+ inode.XattrInline = make([]byte, inodeExtraSize) |
|
| 320 |
+ binary.LittleEndian.PutUint32(inode.XattrInline[0:], format.XAttrHeaderMagic) // Magic |
|
| 321 |
+ putXattrs(state.inode, inode.XattrInline[4:], 0) |
|
| 322 |
+ } |
|
| 323 |
+ |
|
| 324 |
+ // Write the block attributes. If there was previously an xattr block, then |
|
| 325 |
+ // rewrite it even if it is now empty. |
|
| 326 |
+ if len(state.block) != 0 || inode.XattrBlock != 0 {
|
|
| 327 |
+ sort.Slice(state.block, func(i, j int) bool {
|
|
| 328 |
+ return state.block[i].Index < state.block[j].Index || |
|
| 329 |
+ len(state.block[i].Name) < len(state.block[j].Name) || |
|
| 330 |
+ state.block[i].Name < state.block[j].Name |
|
| 331 |
+ }) |
|
| 332 |
+ |
|
| 333 |
+ var b [blockSize]byte |
|
| 334 |
+ binary.LittleEndian.PutUint32(b[0:], format.XAttrHeaderMagic) // Magic |
|
| 335 |
+ binary.LittleEndian.PutUint32(b[4:], 1) // ReferenceCount |
|
| 336 |
+ binary.LittleEndian.PutUint32(b[8:], 1) // Blocks |
|
| 337 |
+ putXattrs(state.block, b[32:], 32) |
|
| 338 |
+ |
|
| 339 |
+ orig := w.block() |
|
| 340 |
+ if inode.XattrBlock == 0 {
|
|
| 341 |
+ inode.XattrBlock = orig |
|
| 342 |
+ inode.BlockCount++ |
|
| 343 |
+ } else {
|
|
| 344 |
+ // Reuse the original block. |
|
| 345 |
+ w.seekBlock(inode.XattrBlock) |
|
| 346 |
+ defer w.seekBlock(orig) |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ if _, err := w.write(b[:]); err != nil {
|
|
| 350 |
+ return err |
|
| 351 |
+ } |
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ return nil |
|
| 355 |
+} |
|
| 356 |
+ |
|
| 357 |
+func (w *Writer) write(b []byte) (int, error) {
|
|
| 358 |
+ if w.err != nil {
|
|
| 359 |
+ return 0, w.err |
|
| 360 |
+ } |
|
| 361 |
+ if w.pos+int64(len(b)) > w.maxDiskSize {
|
|
| 362 |
+ w.err = exceededMaxSizeError{w.maxDiskSize}
|
|
| 363 |
+ return 0, w.err |
|
| 364 |
+ } |
|
| 365 |
+ n, err := w.bw.Write(b) |
|
| 366 |
+ w.pos += int64(n) |
|
| 367 |
+ w.err = err |
|
| 368 |
+ return n, err |
|
| 369 |
+} |
|
| 370 |
+ |
|
| 371 |
+func (w *Writer) zero(n int64) (int64, error) {
|
|
| 372 |
+ if w.err != nil {
|
|
| 373 |
+ return 0, w.err |
|
| 374 |
+ } |
|
| 375 |
+ if w.pos+int64(n) > w.maxDiskSize {
|
|
| 376 |
+ w.err = exceededMaxSizeError{w.maxDiskSize}
|
|
| 377 |
+ return 0, w.err |
|
| 378 |
+ } |
|
| 379 |
+ n, err := io.CopyN(w.bw, zero, n) |
|
| 380 |
+ w.pos += n |
|
| 381 |
+ w.err = err |
|
| 382 |
+ return n, err |
|
| 383 |
+} |
|
| 384 |
+ |
|
| 385 |
+func (w *Writer) makeInode(f *File, node *inode) (*inode, error) {
|
|
| 386 |
+ mode := f.Mode |
|
| 387 |
+ if mode&format.TypeMask == 0 {
|
|
| 388 |
+ mode |= format.S_IFREG |
|
| 389 |
+ } |
|
| 390 |
+ typ := mode & format.TypeMask |
|
| 391 |
+ ino := format.InodeNumber(len(w.inodes) + 1) |
|
| 392 |
+ if node == nil {
|
|
| 393 |
+ node = &inode{
|
|
| 394 |
+ Number: ino, |
|
| 395 |
+ } |
|
| 396 |
+ if typ == S_IFDIR {
|
|
| 397 |
+ node.Children = make(directory) |
|
| 398 |
+ node.LinkCount = 1 // A directory is linked to itself. |
|
| 399 |
+ } |
|
| 400 |
+ } else if node.Flags&format.InodeFlagExtents != 0 {
|
|
| 401 |
+ // Since we cannot deallocate or reuse blocks, don't allow updates that |
|
| 402 |
+ // would invalidate data that has already been written. |
|
| 403 |
+ return nil, errors.New("cannot overwrite file with non-inline data")
|
|
| 404 |
+ } |
|
| 405 |
+ node.Mode = mode |
|
| 406 |
+ node.Uid = f.Uid |
|
| 407 |
+ node.Gid = f.Gid |
|
| 408 |
+ node.Flags = format.InodeFlagHugeFile |
|
| 409 |
+ node.Atime = timeToFsTime(f.Atime) |
|
| 410 |
+ node.Ctime = timeToFsTime(f.Ctime) |
|
| 411 |
+ node.Mtime = timeToFsTime(f.Mtime) |
|
| 412 |
+ node.Crtime = timeToFsTime(f.Crtime) |
|
| 413 |
+ node.Devmajor = f.Devmajor |
|
| 414 |
+ node.Devminor = f.Devminor |
|
| 415 |
+ node.Data = nil |
|
| 416 |
+ node.XattrInline = nil |
|
| 417 |
+ |
|
| 418 |
+ var xstate xattrState |
|
| 419 |
+ xstate.init() |
|
| 420 |
+ |
|
| 421 |
+ var size int64 |
|
| 422 |
+ switch typ {
|
|
| 423 |
+ case format.S_IFREG: |
|
| 424 |
+ size = f.Size |
|
| 425 |
+ if f.Size > maxFileSize {
|
|
| 426 |
+ return nil, fmt.Errorf("file too big: %d > %d", f.Size, maxFileSize)
|
|
| 427 |
+ } |
|
| 428 |
+ if f.Size <= inlineDataSize && w.supportInlineData {
|
|
| 429 |
+ node.Data = make([]byte, f.Size) |
|
| 430 |
+ extra := 0 |
|
| 431 |
+ if f.Size > inodeDataSize {
|
|
| 432 |
+ extra = int(f.Size - inodeDataSize) |
|
| 433 |
+ } |
|
| 434 |
+ // Add a dummy entry for now. |
|
| 435 |
+ if !xstate.addXattr("system.data", node.Data[:extra]) {
|
|
| 436 |
+ panic("not enough room for inline data")
|
|
| 437 |
+ } |
|
| 438 |
+ node.Flags |= format.InodeFlagInlineData |
|
| 439 |
+ } |
|
| 440 |
+ case format.S_IFLNK: |
|
| 441 |
+ node.Mode |= 0777 // Symlinks should appear as ugw rwx |
|
| 442 |
+ size = int64(len(f.Linkname)) |
|
| 443 |
+ if size <= smallSymlinkSize {
|
|
| 444 |
+ // Special case: small symlinks go directly in Block without setting |
|
| 445 |
+ // an inline data flag. |
|
| 446 |
+ node.Data = make([]byte, len(f.Linkname)) |
|
| 447 |
+ copy(node.Data, f.Linkname) |
|
| 448 |
+ } |
|
| 449 |
+ case format.S_IFDIR, format.S_IFIFO, format.S_IFSOCK, format.S_IFCHR, format.S_IFBLK: |
|
| 450 |
+ default: |
|
| 451 |
+ return nil, fmt.Errorf("invalid mode %o", mode)
|
|
| 452 |
+ } |
|
| 453 |
+ |
|
| 454 |
+ // Accumulate the extended attributes. |
|
| 455 |
+ if len(f.Xattrs) != 0 {
|
|
| 456 |
+ // Sort the xattrs to avoid non-determinism in map iteration. |
|
| 457 |
+ var xattrs []string |
|
| 458 |
+ for name := range f.Xattrs {
|
|
| 459 |
+ xattrs = append(xattrs, name) |
|
| 460 |
+ } |
|
| 461 |
+ sort.Strings(xattrs) |
|
| 462 |
+ for _, name := range xattrs {
|
|
| 463 |
+ if !xstate.addXattr(name, f.Xattrs[name]) {
|
|
| 464 |
+ return nil, fmt.Errorf("could not fit xattr %s", name)
|
|
| 465 |
+ } |
|
| 466 |
+ } |
|
| 467 |
+ } |
|
| 468 |
+ |
|
| 469 |
+ if err := w.writeXattrs(node, &xstate); err != nil {
|
|
| 470 |
+ return nil, err |
|
| 471 |
+ } |
|
| 472 |
+ |
|
| 473 |
+ node.Size = size |
|
| 474 |
+ if typ == format.S_IFLNK && size > smallSymlinkSize {
|
|
| 475 |
+ // Write the link name as data. |
|
| 476 |
+ w.startInode("", node, size)
|
|
| 477 |
+ if _, err := w.Write([]byte(f.Linkname)); err != nil {
|
|
| 478 |
+ return nil, err |
|
| 479 |
+ } |
|
| 480 |
+ if err := w.finishInode(); err != nil {
|
|
| 481 |
+ return nil, err |
|
| 482 |
+ } |
|
| 483 |
+ } |
|
| 484 |
+ |
|
| 485 |
+ if int(node.Number-1) >= len(w.inodes) {
|
|
| 486 |
+ w.inodes = append(w.inodes, node) |
|
| 487 |
+ } |
|
| 488 |
+ return node, nil |
|
| 489 |
+} |
|
| 490 |
+ |
|
| 491 |
+func (w *Writer) root() *inode {
|
|
| 492 |
+ return w.getInode(format.InodeRoot) |
|
| 493 |
+} |
|
| 494 |
+ |
|
| 495 |
+func (w *Writer) lookup(name string, mustExist bool) (*inode, *inode, string, error) {
|
|
| 496 |
+ root := w.root() |
|
| 497 |
+ cleanname := path.Clean("/" + name)[1:]
|
|
| 498 |
+ if len(cleanname) == 0 {
|
|
| 499 |
+ return root, root, "", nil |
|
| 500 |
+ } |
|
| 501 |
+ dirname, childname := path.Split(cleanname) |
|
| 502 |
+ if len(childname) == 0 || len(childname) > 0xff {
|
|
| 503 |
+ return nil, nil, "", fmt.Errorf("%s: invalid name", name)
|
|
| 504 |
+ } |
|
| 505 |
+ dir := w.findPath(root, dirname) |
|
| 506 |
+ if dir == nil || !dir.IsDir() {
|
|
| 507 |
+ return nil, nil, "", fmt.Errorf("%s: path not found", name)
|
|
| 508 |
+ } |
|
| 509 |
+ child := dir.Children[childname] |
|
| 510 |
+ if child == nil && mustExist {
|
|
| 511 |
+ return nil, nil, "", fmt.Errorf("%s: file not found", name)
|
|
| 512 |
+ } |
|
| 513 |
+ return dir, child, childname, nil |
|
| 514 |
+} |
|
| 515 |
+ |
|
| 516 |
+// Create adds a file to the file system. |
|
| 517 |
+func (w *Writer) Create(name string, f *File) error {
|
|
| 518 |
+ if err := w.finishInode(); err != nil {
|
|
| 519 |
+ return err |
|
| 520 |
+ } |
|
| 521 |
+ dir, existing, childname, err := w.lookup(name, false) |
|
| 522 |
+ if err != nil {
|
|
| 523 |
+ return err |
|
| 524 |
+ } |
|
| 525 |
+ var reuse *inode |
|
| 526 |
+ if existing != nil {
|
|
| 527 |
+ if existing.IsDir() {
|
|
| 528 |
+ if f.Mode&TypeMask != S_IFDIR {
|
|
| 529 |
+ return fmt.Errorf("%s: cannot replace a directory with a file", name)
|
|
| 530 |
+ } |
|
| 531 |
+ reuse = existing |
|
| 532 |
+ } else if f.Mode&TypeMask == S_IFDIR {
|
|
| 533 |
+ return fmt.Errorf("%s: cannot replace a file with a directory", name)
|
|
| 534 |
+ } else if existing.LinkCount < 2 {
|
|
| 535 |
+ reuse = existing |
|
| 536 |
+ } |
|
| 537 |
+ } else {
|
|
| 538 |
+ if f.Mode&TypeMask == S_IFDIR && dir.LinkCount >= format.MaxLinks {
|
|
| 539 |
+ return fmt.Errorf("%s: exceeded parent directory maximum link count", name)
|
|
| 540 |
+ } |
|
| 541 |
+ } |
|
| 542 |
+ child, err := w.makeInode(f, reuse) |
|
| 543 |
+ if err != nil {
|
|
| 544 |
+ return fmt.Errorf("%s: %s", name, err)
|
|
| 545 |
+ } |
|
| 546 |
+ if existing != child {
|
|
| 547 |
+ if existing != nil {
|
|
| 548 |
+ existing.LinkCount-- |
|
| 549 |
+ } |
|
| 550 |
+ dir.Children[childname] = child |
|
| 551 |
+ child.LinkCount++ |
|
| 552 |
+ if child.IsDir() {
|
|
| 553 |
+ dir.LinkCount++ |
|
| 554 |
+ } |
|
| 555 |
+ } |
|
| 556 |
+ if child.Mode&format.TypeMask == format.S_IFREG {
|
|
| 557 |
+ w.startInode(name, child, f.Size) |
|
| 558 |
+ } |
|
| 559 |
+ return nil |
|
| 560 |
+} |
|
| 561 |
+ |
|
| 562 |
+// Link adds a hard link to the file system. |
|
| 563 |
+func (w *Writer) Link(oldname, newname string) error {
|
|
| 564 |
+ if err := w.finishInode(); err != nil {
|
|
| 565 |
+ return err |
|
| 566 |
+ } |
|
| 567 |
+ newdir, existing, newchildname, err := w.lookup(newname, false) |
|
| 568 |
+ if err != nil {
|
|
| 569 |
+ return err |
|
| 570 |
+ } |
|
| 571 |
+ if existing != nil && (existing.IsDir() || existing.LinkCount < 2) {
|
|
| 572 |
+ return fmt.Errorf("%s: cannot orphan existing file or directory", newname)
|
|
| 573 |
+ } |
|
| 574 |
+ |
|
| 575 |
+ _, oldfile, _, err := w.lookup(oldname, true) |
|
| 576 |
+ if err != nil {
|
|
| 577 |
+ return err |
|
| 578 |
+ } |
|
| 579 |
+ switch oldfile.Mode & format.TypeMask {
|
|
| 580 |
+ case format.S_IFDIR, format.S_IFLNK: |
|
| 581 |
+ return fmt.Errorf("%s: link target cannot be a directory or symlink: %s", newname, oldname)
|
|
| 582 |
+ } |
|
| 583 |
+ |
|
| 584 |
+ if existing != oldfile && oldfile.LinkCount >= format.MaxLinks {
|
|
| 585 |
+ return fmt.Errorf("%s: link target would exceed maximum link count: %s", newname, oldname)
|
|
| 586 |
+ } |
|
| 587 |
+ |
|
| 588 |
+ if existing != nil {
|
|
| 589 |
+ existing.LinkCount-- |
|
| 590 |
+ } |
|
| 591 |
+ oldfile.LinkCount++ |
|
| 592 |
+ newdir.Children[newchildname] = oldfile |
|
| 593 |
+ return nil |
|
| 594 |
+} |
|
| 595 |
+ |
|
| 596 |
+// Stat returns information about a file that has been written. |
|
| 597 |
+func (w *Writer) Stat(name string) (*File, error) {
|
|
| 598 |
+ if err := w.finishInode(); err != nil {
|
|
| 599 |
+ return nil, err |
|
| 600 |
+ } |
|
| 601 |
+ _, node, _, err := w.lookup(name, true) |
|
| 602 |
+ if err != nil {
|
|
| 603 |
+ return nil, err |
|
| 604 |
+ } |
|
| 605 |
+ f := &File{
|
|
| 606 |
+ Size: node.Size, |
|
| 607 |
+ Mode: node.Mode, |
|
| 608 |
+ Uid: node.Uid, |
|
| 609 |
+ Gid: node.Gid, |
|
| 610 |
+ Atime: fsTimeToTime(node.Atime), |
|
| 611 |
+ Ctime: fsTimeToTime(node.Ctime), |
|
| 612 |
+ Mtime: fsTimeToTime(node.Mtime), |
|
| 613 |
+ Crtime: fsTimeToTime(node.Crtime), |
|
| 614 |
+ Devmajor: node.Devmajor, |
|
| 615 |
+ Devminor: node.Devminor, |
|
| 616 |
+ } |
|
| 617 |
+ f.Xattrs = make(map[string][]byte) |
|
| 618 |
+ if node.XattrBlock != 0 || len(node.XattrInline) != 0 {
|
|
| 619 |
+ if node.XattrBlock != 0 {
|
|
| 620 |
+ orig := w.block() |
|
| 621 |
+ w.seekBlock(node.XattrBlock) |
|
| 622 |
+ if w.err != nil {
|
|
| 623 |
+ return nil, w.err |
|
| 624 |
+ } |
|
| 625 |
+ var b [blockSize]byte |
|
| 626 |
+ _, err := w.f.Read(b[:]) |
|
| 627 |
+ w.seekBlock(orig) |
|
| 628 |
+ if err != nil {
|
|
| 629 |
+ return nil, err |
|
| 630 |
+ } |
|
| 631 |
+ getXattrs(b[32:], f.Xattrs, 32) |
|
| 632 |
+ } |
|
| 633 |
+ if len(node.XattrInline) != 0 {
|
|
| 634 |
+ getXattrs(node.XattrInline[4:], f.Xattrs, 0) |
|
| 635 |
+ delete(f.Xattrs, "system.data") |
|
| 636 |
+ } |
|
| 637 |
+ } |
|
| 638 |
+ if node.FileType() == S_IFLNK {
|
|
| 639 |
+ if node.Size > smallSymlinkSize {
|
|
| 640 |
+ return nil, fmt.Errorf("%s: cannot retrieve link information", name)
|
|
| 641 |
+ } |
|
| 642 |
+ f.Linkname = string(node.Data) |
|
| 643 |
+ } |
|
| 644 |
+ return f, nil |
|
| 645 |
+} |
|
| 646 |
+ |
|
| 647 |
+func (w *Writer) Write(b []byte) (int, error) {
|
|
| 648 |
+ if len(b) == 0 {
|
|
| 649 |
+ return 0, nil |
|
| 650 |
+ } |
|
| 651 |
+ if w.dataWritten+int64(len(b)) > w.dataMax {
|
|
| 652 |
+ return 0, fmt.Errorf("%s: wrote too much: %d > %d", w.curName, w.dataWritten+int64(len(b)), w.dataMax)
|
|
| 653 |
+ } |
|
| 654 |
+ |
|
| 655 |
+ if w.curInode.Flags&format.InodeFlagInlineData != 0 {
|
|
| 656 |
+ copy(w.curInode.Data[w.dataWritten:], b) |
|
| 657 |
+ w.dataWritten += int64(len(b)) |
|
| 658 |
+ return len(b), nil |
|
| 659 |
+ } |
|
| 660 |
+ |
|
| 661 |
+ n, err := w.write(b) |
|
| 662 |
+ w.dataWritten += int64(n) |
|
| 663 |
+ return n, err |
|
| 664 |
+} |
|
| 665 |
+ |
|
| 666 |
+func (w *Writer) startInode(name string, inode *inode, size int64) {
|
|
| 667 |
+ if w.curInode != nil {
|
|
| 668 |
+ panic("inode already in progress")
|
|
| 669 |
+ } |
|
| 670 |
+ w.curName = name |
|
| 671 |
+ w.curInode = inode |
|
| 672 |
+ w.dataWritten = 0 |
|
| 673 |
+ w.dataMax = size |
|
| 674 |
+} |
|
| 675 |
+ |
|
| 676 |
+func (w *Writer) block() uint32 {
|
|
| 677 |
+ return uint32(w.pos / blockSize) |
|
| 678 |
+} |
|
| 679 |
+ |
|
| 680 |
+func (w *Writer) seekBlock(block uint32) {
|
|
| 681 |
+ w.pos = int64(block) * blockSize |
|
| 682 |
+ if w.err != nil {
|
|
| 683 |
+ return |
|
| 684 |
+ } |
|
| 685 |
+ w.err = w.bw.Flush() |
|
| 686 |
+ if w.err != nil {
|
|
| 687 |
+ return |
|
| 688 |
+ } |
|
| 689 |
+ _, w.err = w.f.Seek(w.pos, io.SeekStart) |
|
| 690 |
+} |
|
| 691 |
+ |
|
| 692 |
+func (w *Writer) nextBlock() {
|
|
| 693 |
+ if w.pos%blockSize != 0 {
|
|
| 694 |
+ // Simplify callers; w.err is updated on failure. |
|
| 695 |
+ w.zero(blockSize - w.pos%blockSize) |
|
| 696 |
+ } |
|
| 697 |
+} |
|
| 698 |
+ |
|
| 699 |
+func fillExtents(hdr *format.ExtentHeader, extents []format.ExtentLeafNode, startBlock, offset, inodeSize uint32) {
|
|
| 700 |
+ *hdr = format.ExtentHeader{
|
|
| 701 |
+ Magic: format.ExtentHeaderMagic, |
|
| 702 |
+ Entries: uint16(len(extents)), |
|
| 703 |
+ Max: uint16(cap(extents)), |
|
| 704 |
+ Depth: 0, |
|
| 705 |
+ } |
|
| 706 |
+ for i := range extents {
|
|
| 707 |
+ block := offset + uint32(i)*maxBlocksPerExtent |
|
| 708 |
+ length := inodeSize - block |
|
| 709 |
+ if length > maxBlocksPerExtent {
|
|
| 710 |
+ length = maxBlocksPerExtent |
|
| 711 |
+ } |
|
| 712 |
+ start := startBlock + block |
|
| 713 |
+ extents[i] = format.ExtentLeafNode{
|
|
| 714 |
+ Block: block, |
|
| 715 |
+ Length: uint16(length), |
|
| 716 |
+ StartLow: start, |
|
| 717 |
+ } |
|
| 718 |
+ } |
|
| 719 |
+} |
|
| 720 |
+ |
|
| 721 |
+func (w *Writer) writeExtents(inode *inode) error {
|
|
| 722 |
+ start := w.pos - w.dataWritten |
|
| 723 |
+ if start%blockSize != 0 {
|
|
| 724 |
+ panic("unaligned")
|
|
| 725 |
+ } |
|
| 726 |
+ w.nextBlock() |
|
| 727 |
+ |
|
| 728 |
+ startBlock := uint32(start / blockSize) |
|
| 729 |
+ blocks := w.block() - startBlock |
|
| 730 |
+ usedBlocks := blocks |
|
| 731 |
+ |
|
| 732 |
+ const extentNodeSize = 12 |
|
| 733 |
+ const extentsPerBlock = blockSize/extentNodeSize - 1 |
|
| 734 |
+ |
|
| 735 |
+ extents := (blocks + maxBlocksPerExtent - 1) / maxBlocksPerExtent |
|
| 736 |
+ var b bytes.Buffer |
|
| 737 |
+ if extents == 0 {
|
|
| 738 |
+ // Nothing to do. |
|
| 739 |
+ } else if extents <= 4 {
|
|
| 740 |
+ var root struct {
|
|
| 741 |
+ hdr format.ExtentHeader |
|
| 742 |
+ extents [4]format.ExtentLeafNode |
|
| 743 |
+ } |
|
| 744 |
+ fillExtents(&root.hdr, root.extents[:extents], startBlock, 0, blocks) |
|
| 745 |
+ binary.Write(&b, binary.LittleEndian, root) |
|
| 746 |
+ } else if extents <= 4*extentsPerBlock {
|
|
| 747 |
+ const extentsPerBlock = blockSize/extentNodeSize - 1 |
|
| 748 |
+ extentBlocks := extents/extentsPerBlock + 1 |
|
| 749 |
+ usedBlocks += extentBlocks |
|
| 750 |
+ var b2 bytes.Buffer |
|
| 751 |
+ |
|
| 752 |
+ var root struct {
|
|
| 753 |
+ hdr format.ExtentHeader |
|
| 754 |
+ nodes [4]format.ExtentIndexNode |
|
| 755 |
+ } |
|
| 756 |
+ root.hdr = format.ExtentHeader{
|
|
| 757 |
+ Magic: format.ExtentHeaderMagic, |
|
| 758 |
+ Entries: uint16(extentBlocks), |
|
| 759 |
+ Max: 4, |
|
| 760 |
+ Depth: 1, |
|
| 761 |
+ } |
|
| 762 |
+ for i := uint32(0); i < extentBlocks; i++ {
|
|
| 763 |
+ root.nodes[i] = format.ExtentIndexNode{
|
|
| 764 |
+ Block: i * extentsPerBlock * maxBlocksPerExtent, |
|
| 765 |
+ LeafLow: w.block(), |
|
| 766 |
+ } |
|
| 767 |
+ extentsInBlock := extents - i*extentBlocks |
|
| 768 |
+ if extentsInBlock > extentsPerBlock {
|
|
| 769 |
+ extentsInBlock = extentsPerBlock |
|
| 770 |
+ } |
|
| 771 |
+ |
|
| 772 |
+ var node struct {
|
|
| 773 |
+ hdr format.ExtentHeader |
|
| 774 |
+ extents [extentsPerBlock]format.ExtentLeafNode |
|
| 775 |
+ _ [blockSize - (extentsPerBlock+1)*extentNodeSize]byte |
|
| 776 |
+ } |
|
| 777 |
+ |
|
| 778 |
+ offset := i * extentsPerBlock * maxBlocksPerExtent |
|
| 779 |
+ fillExtents(&node.hdr, node.extents[:extentsInBlock], startBlock+offset, offset, blocks) |
|
| 780 |
+ binary.Write(&b2, binary.LittleEndian, node) |
|
| 781 |
+ if _, err := w.write(b2.Next(blockSize)); err != nil {
|
|
| 782 |
+ return err |
|
| 783 |
+ } |
|
| 784 |
+ } |
|
| 785 |
+ binary.Write(&b, binary.LittleEndian, root) |
|
| 786 |
+ } else {
|
|
| 787 |
+ panic("file too big")
|
|
| 788 |
+ } |
|
| 789 |
+ |
|
| 790 |
+ inode.Data = b.Bytes() |
|
| 791 |
+ inode.Flags |= format.InodeFlagExtents |
|
| 792 |
+ inode.BlockCount += usedBlocks |
|
| 793 |
+ return w.err |
|
| 794 |
+} |
|
| 795 |
+ |
|
| 796 |
+func (w *Writer) finishInode() error {
|
|
| 797 |
+ if !w.initialized {
|
|
| 798 |
+ if err := w.init(); err != nil {
|
|
| 799 |
+ return err |
|
| 800 |
+ } |
|
| 801 |
+ } |
|
| 802 |
+ if w.curInode == nil {
|
|
| 803 |
+ return nil |
|
| 804 |
+ } |
|
| 805 |
+ if w.dataWritten != w.dataMax {
|
|
| 806 |
+ return fmt.Errorf("did not write the right amount: %d != %d", w.dataWritten, w.dataMax)
|
|
| 807 |
+ } |
|
| 808 |
+ |
|
| 809 |
+ if w.dataMax != 0 && w.curInode.Flags&format.InodeFlagInlineData == 0 {
|
|
| 810 |
+ if err := w.writeExtents(w.curInode); err != nil {
|
|
| 811 |
+ return err |
|
| 812 |
+ } |
|
| 813 |
+ } |
|
| 814 |
+ |
|
| 815 |
+ w.dataWritten = 0 |
|
| 816 |
+ w.dataMax = 0 |
|
| 817 |
+ w.curInode = nil |
|
| 818 |
+ return w.err |
|
| 819 |
+} |
|
| 820 |
+ |
|
| 821 |
+func modeToFileType(mode uint16) format.FileType {
|
|
| 822 |
+ switch mode & format.TypeMask {
|
|
| 823 |
+ default: |
|
| 824 |
+ return format.FileTypeUnknown |
|
| 825 |
+ case format.S_IFREG: |
|
| 826 |
+ return format.FileTypeRegular |
|
| 827 |
+ case format.S_IFDIR: |
|
| 828 |
+ return format.FileTypeDirectory |
|
| 829 |
+ case format.S_IFCHR: |
|
| 830 |
+ return format.FileTypeCharacter |
|
| 831 |
+ case format.S_IFBLK: |
|
| 832 |
+ return format.FileTypeBlock |
|
| 833 |
+ case format.S_IFIFO: |
|
| 834 |
+ return format.FileTypeFIFO |
|
| 835 |
+ case format.S_IFSOCK: |
|
| 836 |
+ return format.FileTypeSocket |
|
| 837 |
+ case format.S_IFLNK: |
|
| 838 |
+ return format.FileTypeSymbolicLink |
|
| 839 |
+ } |
|
| 840 |
+} |
|
| 841 |
+ |
|
| 842 |
+type constReader byte |
|
| 843 |
+ |
|
| 844 |
+var zero = constReader(0) |
|
| 845 |
+ |
|
| 846 |
+func (r constReader) Read(b []byte) (int, error) {
|
|
| 847 |
+ for i := range b {
|
|
| 848 |
+ b[i] = byte(r) |
|
| 849 |
+ } |
|
| 850 |
+ return len(b), nil |
|
| 851 |
+} |
|
| 852 |
+ |
|
| 853 |
+func (w *Writer) writeDirectory(dir, parent *inode) error {
|
|
| 854 |
+ if err := w.finishInode(); err != nil {
|
|
| 855 |
+ return err |
|
| 856 |
+ } |
|
| 857 |
+ |
|
| 858 |
+ // The size of the directory is not known yet. |
|
| 859 |
+ w.startInode("", dir, 0x7fffffffffffffff)
|
|
| 860 |
+ left := blockSize |
|
| 861 |
+ finishBlock := func() error {
|
|
| 862 |
+ if left > 0 {
|
|
| 863 |
+ e := format.DirectoryEntry{
|
|
| 864 |
+ RecordLength: uint16(left), |
|
| 865 |
+ } |
|
| 866 |
+ err := binary.Write(w, binary.LittleEndian, e) |
|
| 867 |
+ if err != nil {
|
|
| 868 |
+ return err |
|
| 869 |
+ } |
|
| 870 |
+ left -= directoryEntrySize |
|
| 871 |
+ if left < 4 {
|
|
| 872 |
+ panic("not enough space for trailing entry")
|
|
| 873 |
+ } |
|
| 874 |
+ _, err = io.CopyN(w, zero, int64(left)) |
|
| 875 |
+ if err != nil {
|
|
| 876 |
+ return err |
|
| 877 |
+ } |
|
| 878 |
+ } |
|
| 879 |
+ left = blockSize |
|
| 880 |
+ return nil |
|
| 881 |
+ } |
|
| 882 |
+ |
|
| 883 |
+ writeEntry := func(ino format.InodeNumber, name string) error {
|
|
| 884 |
+ rlb := directoryEntrySize + len(name) |
|
| 885 |
+ rl := (rlb + 3) & ^3 |
|
| 886 |
+ if left < rl+12 {
|
|
| 887 |
+ if err := finishBlock(); err != nil {
|
|
| 888 |
+ return err |
|
| 889 |
+ } |
|
| 890 |
+ } |
|
| 891 |
+ e := format.DirectoryEntry{
|
|
| 892 |
+ Inode: ino, |
|
| 893 |
+ RecordLength: uint16(rl), |
|
| 894 |
+ NameLength: uint8(len(name)), |
|
| 895 |
+ FileType: modeToFileType(w.getInode(ino).Mode), |
|
| 896 |
+ } |
|
| 897 |
+ err := binary.Write(w, binary.LittleEndian, e) |
|
| 898 |
+ if err != nil {
|
|
| 899 |
+ return err |
|
| 900 |
+ } |
|
| 901 |
+ _, err = w.Write([]byte(name)) |
|
| 902 |
+ if err != nil {
|
|
| 903 |
+ return err |
|
| 904 |
+ } |
|
| 905 |
+ var zero [4]byte |
|
| 906 |
+ _, err = w.Write(zero[:rl-rlb]) |
|
| 907 |
+ if err != nil {
|
|
| 908 |
+ return err |
|
| 909 |
+ } |
|
| 910 |
+ left -= rl |
|
| 911 |
+ return nil |
|
| 912 |
+ } |
|
| 913 |
+ if err := writeEntry(dir.Number, "."); err != nil {
|
|
| 914 |
+ return err |
|
| 915 |
+ } |
|
| 916 |
+ if err := writeEntry(parent.Number, ".."); err != nil {
|
|
| 917 |
+ return err |
|
| 918 |
+ } |
|
| 919 |
+ |
|
| 920 |
+ // Follow e2fsck's convention and sort the children by inode number. |
|
| 921 |
+ var children []string |
|
| 922 |
+ for name := range dir.Children {
|
|
| 923 |
+ children = append(children, name) |
|
| 924 |
+ } |
|
| 925 |
+ sort.Slice(children, func(i, j int) bool {
|
|
| 926 |
+ return dir.Children[children[i]].Number < dir.Children[children[j]].Number |
|
| 927 |
+ }) |
|
| 928 |
+ |
|
| 929 |
+ for _, name := range children {
|
|
| 930 |
+ child := dir.Children[name] |
|
| 931 |
+ if err := writeEntry(child.Number, name); err != nil {
|
|
| 932 |
+ return err |
|
| 933 |
+ } |
|
| 934 |
+ } |
|
| 935 |
+ if err := finishBlock(); err != nil {
|
|
| 936 |
+ return err |
|
| 937 |
+ } |
|
| 938 |
+ w.curInode.Size = w.dataWritten |
|
| 939 |
+ w.dataMax = w.dataWritten |
|
| 940 |
+ return nil |
|
| 941 |
+} |
|
| 942 |
+ |
|
| 943 |
+func (w *Writer) writeDirectoryRecursive(dir, parent *inode) error {
|
|
| 944 |
+ if err := w.writeDirectory(dir, parent); err != nil {
|
|
| 945 |
+ return err |
|
| 946 |
+ } |
|
| 947 |
+ for _, child := range dir.Children {
|
|
| 948 |
+ if child.IsDir() {
|
|
| 949 |
+ if err := w.writeDirectoryRecursive(child, dir); err != nil {
|
|
| 950 |
+ return err |
|
| 951 |
+ } |
|
| 952 |
+ } |
|
| 953 |
+ } |
|
| 954 |
+ return nil |
|
| 955 |
+} |
|
| 956 |
+ |
|
| 957 |
+func (w *Writer) writeInodeTable(tableSize uint32) error {
|
|
| 958 |
+ var b bytes.Buffer |
|
| 959 |
+ for _, inode := range w.inodes {
|
|
| 960 |
+ if inode != nil {
|
|
| 961 |
+ binode := format.Inode{
|
|
| 962 |
+ Mode: inode.Mode, |
|
| 963 |
+ Uid: uint16(inode.Uid & 0xffff), |
|
| 964 |
+ Gid: uint16(inode.Gid & 0xffff), |
|
| 965 |
+ SizeLow: uint32(inode.Size & 0xffffffff), |
|
| 966 |
+ SizeHigh: uint32(inode.Size >> 32), |
|
| 967 |
+ LinksCount: uint16(inode.LinkCount), |
|
| 968 |
+ BlocksLow: inode.BlockCount, |
|
| 969 |
+ Flags: inode.Flags, |
|
| 970 |
+ XattrBlockLow: inode.XattrBlock, |
|
| 971 |
+ UidHigh: uint16(inode.Uid >> 16), |
|
| 972 |
+ GidHigh: uint16(inode.Gid >> 16), |
|
| 973 |
+ ExtraIsize: uint16(inodeUsedSize - 128), |
|
| 974 |
+ Atime: uint32(inode.Atime), |
|
| 975 |
+ AtimeExtra: uint32(inode.Atime >> 32), |
|
| 976 |
+ Ctime: uint32(inode.Ctime), |
|
| 977 |
+ CtimeExtra: uint32(inode.Ctime >> 32), |
|
| 978 |
+ Mtime: uint32(inode.Mtime), |
|
| 979 |
+ MtimeExtra: uint32(inode.Mtime >> 32), |
|
| 980 |
+ Crtime: uint32(inode.Crtime), |
|
| 981 |
+ CrtimeExtra: uint32(inode.Crtime >> 32), |
|
| 982 |
+ } |
|
| 983 |
+ switch inode.Mode & format.TypeMask {
|
|
| 984 |
+ case format.S_IFDIR, format.S_IFREG, format.S_IFLNK: |
|
| 985 |
+ n := copy(binode.Block[:], inode.Data) |
|
| 986 |
+ if n < len(inode.Data) {
|
|
| 987 |
+ // Rewrite the first xattr with the data. |
|
| 988 |
+ xattr := [1]xattr{{
|
|
| 989 |
+ Name: "data", |
|
| 990 |
+ Index: 7, // "system." |
|
| 991 |
+ Value: inode.Data[n:], |
|
| 992 |
+ }} |
|
| 993 |
+ putXattrs(xattr[:], inode.XattrInline[4:], 0) |
|
| 994 |
+ } |
|
| 995 |
+ case format.S_IFBLK, format.S_IFCHR: |
|
| 996 |
+ dev := inode.Devminor&0xff | inode.Devmajor<<8 | (inode.Devminor&0xffffff00)<<12 |
|
| 997 |
+ binary.LittleEndian.PutUint32(binode.Block[4:], dev) |
|
| 998 |
+ } |
|
| 999 |
+ |
|
| 1000 |
+ binary.Write(&b, binary.LittleEndian, binode) |
|
| 1001 |
+ b.Truncate(inodeUsedSize) |
|
| 1002 |
+ n, _ := b.Write(inode.XattrInline) |
|
| 1003 |
+ io.CopyN(&b, zero, int64(inodeExtraSize-n)) |
|
| 1004 |
+ } else {
|
|
| 1005 |
+ io.CopyN(&b, zero, inodeSize) |
|
| 1006 |
+ } |
|
| 1007 |
+ if _, err := w.write(b.Next(inodeSize)); err != nil {
|
|
| 1008 |
+ return err |
|
| 1009 |
+ } |
|
| 1010 |
+ } |
|
| 1011 |
+ rest := tableSize - uint32(len(w.inodes)*inodeSize) |
|
| 1012 |
+ if _, err := w.zero(int64(rest)); err != nil {
|
|
| 1013 |
+ return err |
|
| 1014 |
+ } |
|
| 1015 |
+ return nil |
|
| 1016 |
+} |
|
| 1017 |
+ |
|
| 1018 |
+// NewWriter returns a Writer that writes an ext4 file system to the provided |
|
| 1019 |
+// WriteSeeker. |
|
| 1020 |
+func NewWriter(f io.ReadWriteSeeker, opts ...Option) *Writer {
|
|
| 1021 |
+ w := &Writer{
|
|
| 1022 |
+ f: f, |
|
| 1023 |
+ bw: bufio.NewWriterSize(f, 65536*8), |
|
| 1024 |
+ maxDiskSize: defaultMaxDiskSize, |
|
| 1025 |
+ } |
|
| 1026 |
+ for _, opt := range opts {
|
|
| 1027 |
+ opt(w) |
|
| 1028 |
+ } |
|
| 1029 |
+ return w |
|
| 1030 |
+} |
|
| 1031 |
+ |
|
| 1032 |
+// An Option provides extra options to NewWriter. |
|
| 1033 |
+type Option func(*Writer) |
|
| 1034 |
+ |
|
| 1035 |
+// InlineData instructs the Writer to write small files into the inode |
|
| 1036 |
+// structures directly. This creates smaller images but currently is not |
|
| 1037 |
+// compatible with DAX. |
|
| 1038 |
+func InlineData(w *Writer) {
|
|
| 1039 |
+ w.supportInlineData = true |
|
| 1040 |
+} |
|
| 1041 |
+ |
|
| 1042 |
+// MaximumDiskSize instructs the writer to reserve enough metadata space for the |
|
| 1043 |
+// specified disk size. If not provided, then 16GB is the default. |
|
| 1044 |
+func MaximumDiskSize(size int64) Option {
|
|
| 1045 |
+ return func(w *Writer) {
|
|
| 1046 |
+ if size < 0 || size > maxMaxDiskSize {
|
|
| 1047 |
+ w.maxDiskSize = maxMaxDiskSize |
|
| 1048 |
+ } else if size == 0 {
|
|
| 1049 |
+ w.maxDiskSize = defaultMaxDiskSize |
|
| 1050 |
+ } else {
|
|
| 1051 |
+ w.maxDiskSize = (size + blockSize - 1) &^ (blockSize - 1) |
|
| 1052 |
+ } |
|
| 1053 |
+ } |
|
| 1054 |
+} |
|
| 1055 |
+ |
|
| 1056 |
+func (w *Writer) init() error {
|
|
| 1057 |
+ // Skip the defective block inode. |
|
| 1058 |
+ w.inodes = make([]*inode, 1, 32) |
|
| 1059 |
+ // Create the root directory. |
|
| 1060 |
+ root, _ := w.makeInode(&File{
|
|
| 1061 |
+ Mode: format.S_IFDIR | 0755, |
|
| 1062 |
+ }, nil) |
|
| 1063 |
+ root.LinkCount++ // The root is linked to itself. |
|
| 1064 |
+ // Skip until the first non-reserved inode. |
|
| 1065 |
+ w.inodes = append(w.inodes, make([]*inode, inodeFirst-len(w.inodes)-1)...) |
|
| 1066 |
+ maxBlocks := (w.maxDiskSize-1)/blockSize + 1 |
|
| 1067 |
+ maxGroups := (maxBlocks-1)/blocksPerGroup + 1 |
|
| 1068 |
+ w.gdBlocks = uint32((maxGroups-1)/groupsPerDescriptorBlock + 1) |
|
| 1069 |
+ |
|
| 1070 |
+ // Skip past the superblock and block descriptor table. |
|
| 1071 |
+ w.seekBlock(1 + w.gdBlocks) |
|
| 1072 |
+ w.initialized = true |
|
| 1073 |
+ |
|
| 1074 |
+ // The lost+found directory is required to exist for e2fsck to pass. |
|
| 1075 |
+ if err := w.Create("lost+found", &File{Mode: format.S_IFDIR | 0700}); err != nil {
|
|
| 1076 |
+ return err |
|
| 1077 |
+ } |
|
| 1078 |
+ return w.err |
|
| 1079 |
+} |
|
| 1080 |
+ |
|
| 1081 |
+func groupCount(blocks uint32, inodes uint32, inodesPerGroup uint32) uint32 {
|
|
| 1082 |
+ inodeBlocksPerGroup := inodesPerGroup * inodeSize / blockSize |
|
| 1083 |
+ dataBlocksPerGroup := blocksPerGroup - inodeBlocksPerGroup - 2 // save room for the bitmaps |
|
| 1084 |
+ |
|
| 1085 |
+ // Increase the block count to ensure there are enough groups for all the |
|
| 1086 |
+ // inodes. |
|
| 1087 |
+ minBlocks := (inodes-1)/inodesPerGroup*dataBlocksPerGroup + 1 |
|
| 1088 |
+ if blocks < minBlocks {
|
|
| 1089 |
+ blocks = minBlocks |
|
| 1090 |
+ } |
|
| 1091 |
+ |
|
| 1092 |
+ return (blocks + dataBlocksPerGroup - 1) / dataBlocksPerGroup |
|
| 1093 |
+} |
|
| 1094 |
+ |
|
| 1095 |
+func bestGroupCount(blocks uint32, inodes uint32) (groups uint32, inodesPerGroup uint32) {
|
|
| 1096 |
+ groups = 0xffffffff |
|
| 1097 |
+ for ipg := uint32(inodesPerGroupIncrement); ipg <= maxInodesPerGroup; ipg += inodesPerGroupIncrement {
|
|
| 1098 |
+ g := groupCount(blocks, inodes, ipg) |
|
| 1099 |
+ if g < groups {
|
|
| 1100 |
+ groups = g |
|
| 1101 |
+ inodesPerGroup = ipg |
|
| 1102 |
+ } |
|
| 1103 |
+ } |
|
| 1104 |
+ return |
|
| 1105 |
+} |
|
| 1106 |
+ |
|
| 1107 |
+func (w *Writer) Close() error {
|
|
| 1108 |
+ if err := w.finishInode(); err != nil {
|
|
| 1109 |
+ return err |
|
| 1110 |
+ } |
|
| 1111 |
+ root := w.root() |
|
| 1112 |
+ if err := w.writeDirectoryRecursive(root, root); err != nil {
|
|
| 1113 |
+ return err |
|
| 1114 |
+ } |
|
| 1115 |
+ // Finish the last inode (probably a directory). |
|
| 1116 |
+ if err := w.finishInode(); err != nil {
|
|
| 1117 |
+ return err |
|
| 1118 |
+ } |
|
| 1119 |
+ |
|
| 1120 |
+ // Write the inode table |
|
| 1121 |
+ inodeTableOffset := w.block() |
|
| 1122 |
+ groups, inodesPerGroup := bestGroupCount(inodeTableOffset, uint32(len(w.inodes))) |
|
| 1123 |
+ err := w.writeInodeTable(groups * inodesPerGroup * inodeSize) |
|
| 1124 |
+ if err != nil {
|
|
| 1125 |
+ return err |
|
| 1126 |
+ } |
|
| 1127 |
+ |
|
| 1128 |
+ // Write the bitmaps. |
|
| 1129 |
+ bitmapOffset := w.block() |
|
| 1130 |
+ bitmapSize := groups * 2 |
|
| 1131 |
+ validDataSize := bitmapOffset + bitmapSize |
|
| 1132 |
+ diskSize := validDataSize |
|
| 1133 |
+ minSize := (groups-1)*blocksPerGroup + 1 |
|
| 1134 |
+ if diskSize < minSize {
|
|
| 1135 |
+ diskSize = minSize |
|
| 1136 |
+ } |
|
| 1137 |
+ |
|
| 1138 |
+ usedGdBlocks := (groups-1)/groupDescriptorSize + 1 |
|
| 1139 |
+ if usedGdBlocks > w.gdBlocks {
|
|
| 1140 |
+ return exceededMaxSizeError{w.maxDiskSize}
|
|
| 1141 |
+ } |
|
| 1142 |
+ |
|
| 1143 |
+ gds := make([]format.GroupDescriptor, w.gdBlocks*groupsPerDescriptorBlock) |
|
| 1144 |
+ inodeTableSizePerGroup := inodesPerGroup * inodeSize / blockSize |
|
| 1145 |
+ var totalUsedBlocks, totalUsedInodes uint32 |
|
| 1146 |
+ for g := uint32(0); g < groups; g++ {
|
|
| 1147 |
+ var b [blockSize * 2]byte |
|
| 1148 |
+ var dirCount, usedInodeCount, usedBlockCount uint16 |
|
| 1149 |
+ |
|
| 1150 |
+ // Block bitmap |
|
| 1151 |
+ if (g+1)*blocksPerGroup <= validDataSize {
|
|
| 1152 |
+ // This group is fully allocated. |
|
| 1153 |
+ for j := range b[:blockSize] {
|
|
| 1154 |
+ b[j] = 0xff |
|
| 1155 |
+ } |
|
| 1156 |
+ usedBlockCount = blocksPerGroup |
|
| 1157 |
+ } else if g*blocksPerGroup < validDataSize {
|
|
| 1158 |
+ for j := uint32(0); j < validDataSize-g*blocksPerGroup; j++ {
|
|
| 1159 |
+ b[j/8] |= 1 << (j % 8) |
|
| 1160 |
+ usedBlockCount++ |
|
| 1161 |
+ } |
|
| 1162 |
+ } |
|
| 1163 |
+ if g == 0 {
|
|
| 1164 |
+ // Unused group descriptor blocks should be cleared. |
|
| 1165 |
+ for j := 1 + usedGdBlocks; j < 1+w.gdBlocks; j++ {
|
|
| 1166 |
+ b[j/8] &^= 1 << (j % 8) |
|
| 1167 |
+ usedBlockCount-- |
|
| 1168 |
+ } |
|
| 1169 |
+ } |
|
| 1170 |
+ if g == groups-1 && diskSize%blocksPerGroup != 0 {
|
|
| 1171 |
+ // Blocks that aren't present in the disk should be marked as |
|
| 1172 |
+ // allocated. |
|
| 1173 |
+ for j := diskSize % blocksPerGroup; j < blocksPerGroup; j++ {
|
|
| 1174 |
+ b[j/8] |= 1 << (j % 8) |
|
| 1175 |
+ usedBlockCount++ |
|
| 1176 |
+ } |
|
| 1177 |
+ } |
|
| 1178 |
+ // Inode bitmap |
|
| 1179 |
+ for j := uint32(0); j < inodesPerGroup; j++ {
|
|
| 1180 |
+ ino := format.InodeNumber(1 + g*inodesPerGroup + j) |
|
| 1181 |
+ inode := w.getInode(ino) |
|
| 1182 |
+ if ino < inodeFirst || inode != nil {
|
|
| 1183 |
+ b[blockSize+j/8] |= 1 << (j % 8) |
|
| 1184 |
+ usedInodeCount++ |
|
| 1185 |
+ } |
|
| 1186 |
+ if inode != nil && inode.Mode&format.TypeMask == format.S_IFDIR {
|
|
| 1187 |
+ dirCount++ |
|
| 1188 |
+ } |
|
| 1189 |
+ } |
|
| 1190 |
+ _, err := w.write(b[:]) |
|
| 1191 |
+ if err != nil {
|
|
| 1192 |
+ return err |
|
| 1193 |
+ } |
|
| 1194 |
+ gds[g] = format.GroupDescriptor{
|
|
| 1195 |
+ BlockBitmapLow: bitmapOffset + 2*g, |
|
| 1196 |
+ InodeBitmapLow: bitmapOffset + 2*g + 1, |
|
| 1197 |
+ InodeTableLow: inodeTableOffset + g*inodeTableSizePerGroup, |
|
| 1198 |
+ UsedDirsCountLow: dirCount, |
|
| 1199 |
+ FreeInodesCountLow: uint16(inodesPerGroup) - usedInodeCount, |
|
| 1200 |
+ FreeBlocksCountLow: blocksPerGroup - usedBlockCount, |
|
| 1201 |
+ } |
|
| 1202 |
+ |
|
| 1203 |
+ totalUsedBlocks += uint32(usedBlockCount) |
|
| 1204 |
+ totalUsedInodes += uint32(usedInodeCount) |
|
| 1205 |
+ } |
|
| 1206 |
+ |
|
| 1207 |
+ // Zero up to the disk size. |
|
| 1208 |
+ _, err = w.zero(int64(diskSize-bitmapOffset-bitmapSize) * blockSize) |
|
| 1209 |
+ if err != nil {
|
|
| 1210 |
+ return err |
|
| 1211 |
+ } |
|
| 1212 |
+ |
|
| 1213 |
+ // Write the block descriptors |
|
| 1214 |
+ w.seekBlock(1) |
|
| 1215 |
+ if w.err != nil {
|
|
| 1216 |
+ return w.err |
|
| 1217 |
+ } |
|
| 1218 |
+ err = binary.Write(w.bw, binary.LittleEndian, gds) |
|
| 1219 |
+ if err != nil {
|
|
| 1220 |
+ return err |
|
| 1221 |
+ } |
|
| 1222 |
+ |
|
| 1223 |
+ // Write the super block |
|
| 1224 |
+ var blk [blockSize]byte |
|
| 1225 |
+ b := bytes.NewBuffer(blk[:1024]) |
|
| 1226 |
+ sb := &format.SuperBlock{
|
|
| 1227 |
+ InodesCount: inodesPerGroup * groups, |
|
| 1228 |
+ BlocksCountLow: diskSize, |
|
| 1229 |
+ FreeBlocksCountLow: blocksPerGroup*groups - totalUsedBlocks, |
|
| 1230 |
+ FreeInodesCount: inodesPerGroup*groups - totalUsedInodes, |
|
| 1231 |
+ FirstDataBlock: 0, |
|
| 1232 |
+ LogBlockSize: 2, // 2^(10 + 2) |
|
| 1233 |
+ LogClusterSize: 2, |
|
| 1234 |
+ BlocksPerGroup: blocksPerGroup, |
|
| 1235 |
+ ClustersPerGroup: blocksPerGroup, |
|
| 1236 |
+ InodesPerGroup: inodesPerGroup, |
|
| 1237 |
+ Magic: format.SuperBlockMagic, |
|
| 1238 |
+ State: 1, // cleanly unmounted |
|
| 1239 |
+ Errors: 1, // continue on error? |
|
| 1240 |
+ CreatorOS: 0, // Linux |
|
| 1241 |
+ RevisionLevel: 1, // dynamic inode sizes |
|
| 1242 |
+ FirstInode: inodeFirst, |
|
| 1243 |
+ LpfInode: inodeLostAndFound, |
|
| 1244 |
+ InodeSize: inodeSize, |
|
| 1245 |
+ FeatureCompat: format.CompatSparseSuper2 | format.CompatExtAttr, |
|
| 1246 |
+ FeatureIncompat: format.IncompatFiletype | format.IncompatExtents | format.IncompatFlexBg, |
|
| 1247 |
+ FeatureRoCompat: format.RoCompatLargeFile | format.RoCompatHugeFile | format.RoCompatExtraIsize | format.RoCompatReadonly, |
|
| 1248 |
+ MinExtraIsize: extraIsize, |
|
| 1249 |
+ WantExtraIsize: extraIsize, |
|
| 1250 |
+ LogGroupsPerFlex: 31, |
|
| 1251 |
+ } |
|
| 1252 |
+ if w.supportInlineData {
|
|
| 1253 |
+ sb.FeatureIncompat |= format.IncompatInlineData |
|
| 1254 |
+ } |
|
| 1255 |
+ binary.Write(b, binary.LittleEndian, sb) |
|
| 1256 |
+ w.seekBlock(0) |
|
| 1257 |
+ if _, err := w.write(blk[:]); err != nil {
|
|
| 1258 |
+ return err |
|
| 1259 |
+ } |
|
| 1260 |
+ w.seekBlock(diskSize) |
|
| 1261 |
+ return w.err |
|
| 1262 |
+} |
| 0 | 1263 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,411 @@ |
| 0 |
+package format |
|
| 1 |
+ |
|
| 2 |
+type SuperBlock struct {
|
|
| 3 |
+ InodesCount uint32 |
|
| 4 |
+ BlocksCountLow uint32 |
|
| 5 |
+ RootBlocksCountLow uint32 |
|
| 6 |
+ FreeBlocksCountLow uint32 |
|
| 7 |
+ FreeInodesCount uint32 |
|
| 8 |
+ FirstDataBlock uint32 |
|
| 9 |
+ LogBlockSize uint32 |
|
| 10 |
+ LogClusterSize uint32 |
|
| 11 |
+ BlocksPerGroup uint32 |
|
| 12 |
+ ClustersPerGroup uint32 |
|
| 13 |
+ InodesPerGroup uint32 |
|
| 14 |
+ Mtime uint32 |
|
| 15 |
+ Wtime uint32 |
|
| 16 |
+ MountCount uint16 |
|
| 17 |
+ MaxMountCount uint16 |
|
| 18 |
+ Magic uint16 |
|
| 19 |
+ State uint16 |
|
| 20 |
+ Errors uint16 |
|
| 21 |
+ MinorRevisionLevel uint16 |
|
| 22 |
+ LastCheck uint32 |
|
| 23 |
+ CheckInterval uint32 |
|
| 24 |
+ CreatorOS uint32 |
|
| 25 |
+ RevisionLevel uint32 |
|
| 26 |
+ DefaultReservedUid uint16 |
|
| 27 |
+ DefaultReservedGid uint16 |
|
| 28 |
+ FirstInode uint32 |
|
| 29 |
+ InodeSize uint16 |
|
| 30 |
+ BlockGroupNr uint16 |
|
| 31 |
+ FeatureCompat CompatFeature |
|
| 32 |
+ FeatureIncompat IncompatFeature |
|
| 33 |
+ FeatureRoCompat RoCompatFeature |
|
| 34 |
+ UUID [16]uint8 |
|
| 35 |
+ VolumeName [16]byte |
|
| 36 |
+ LastMounted [64]byte |
|
| 37 |
+ AlgorithmUsageBitmap uint32 |
|
| 38 |
+ PreallocBlocks uint8 |
|
| 39 |
+ PreallocDirBlocks uint8 |
|
| 40 |
+ ReservedGdtBlocks uint16 |
|
| 41 |
+ JournalUUID [16]uint8 |
|
| 42 |
+ JournalInum uint32 |
|
| 43 |
+ JournalDev uint32 |
|
| 44 |
+ LastOrphan uint32 |
|
| 45 |
+ HashSeed [4]uint32 |
|
| 46 |
+ DefHashVersion uint8 |
|
| 47 |
+ JournalBackupType uint8 |
|
| 48 |
+ DescSize uint16 |
|
| 49 |
+ DefaultMountOpts uint32 |
|
| 50 |
+ FirstMetaBg uint32 |
|
| 51 |
+ MkfsTime uint32 |
|
| 52 |
+ JournalBlocks [17]uint32 |
|
| 53 |
+ BlocksCountHigh uint32 |
|
| 54 |
+ RBlocksCountHigh uint32 |
|
| 55 |
+ FreeBlocksCountHigh uint32 |
|
| 56 |
+ MinExtraIsize uint16 |
|
| 57 |
+ WantExtraIsize uint16 |
|
| 58 |
+ Flags uint32 |
|
| 59 |
+ RaidStride uint16 |
|
| 60 |
+ MmpInterval uint16 |
|
| 61 |
+ MmpBlock uint64 |
|
| 62 |
+ RaidStripeWidth uint32 |
|
| 63 |
+ LogGroupsPerFlex uint8 |
|
| 64 |
+ ChecksumType uint8 |
|
| 65 |
+ ReservedPad uint16 |
|
| 66 |
+ KbytesWritten uint64 |
|
| 67 |
+ SnapshotInum uint32 |
|
| 68 |
+ SnapshotID uint32 |
|
| 69 |
+ SnapshotRBlocksCount uint64 |
|
| 70 |
+ SnapshotList uint32 |
|
| 71 |
+ ErrorCount uint32 |
|
| 72 |
+ FirstErrorTime uint32 |
|
| 73 |
+ FirstErrorInode uint32 |
|
| 74 |
+ FirstErrorBlock uint64 |
|
| 75 |
+ FirstErrorFunc [32]uint8 |
|
| 76 |
+ FirstErrorLine uint32 |
|
| 77 |
+ LastErrorTime uint32 |
|
| 78 |
+ LastErrorInode uint32 |
|
| 79 |
+ LastErrorLine uint32 |
|
| 80 |
+ LastErrorBlock uint64 |
|
| 81 |
+ LastErrorFunc [32]uint8 |
|
| 82 |
+ MountOpts [64]uint8 |
|
| 83 |
+ UserQuotaInum uint32 |
|
| 84 |
+ GroupQuotaInum uint32 |
|
| 85 |
+ OverheadBlocks uint32 |
|
| 86 |
+ BackupBgs [2]uint32 |
|
| 87 |
+ EncryptAlgos [4]uint8 |
|
| 88 |
+ EncryptPwSalt [16]uint8 |
|
| 89 |
+ LpfInode uint32 |
|
| 90 |
+ ProjectQuotaInum uint32 |
|
| 91 |
+ ChecksumSeed uint32 |
|
| 92 |
+ WtimeHigh uint8 |
|
| 93 |
+ MtimeHigh uint8 |
|
| 94 |
+ MkfsTimeHigh uint8 |
|
| 95 |
+ LastcheckHigh uint8 |
|
| 96 |
+ FirstErrorTimeHigh uint8 |
|
| 97 |
+ LastErrorTimeHigh uint8 |
|
| 98 |
+ Pad [2]uint8 |
|
| 99 |
+ Reserved [96]uint32 |
|
| 100 |
+ Checksum uint32 |
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+const SuperBlockMagic uint16 = 0xef53 |
|
| 104 |
+ |
|
| 105 |
+type CompatFeature uint32 |
|
| 106 |
+type IncompatFeature uint32 |
|
| 107 |
+type RoCompatFeature uint32 |
|
| 108 |
+ |
|
| 109 |
+const ( |
|
| 110 |
+ CompatDirPrealloc CompatFeature = 0x1 |
|
| 111 |
+ CompatImagicInodes CompatFeature = 0x2 |
|
| 112 |
+ CompatHasJournal CompatFeature = 0x4 |
|
| 113 |
+ CompatExtAttr CompatFeature = 0x8 |
|
| 114 |
+ CompatResizeInode CompatFeature = 0x10 |
|
| 115 |
+ CompatDirIndex CompatFeature = 0x20 |
|
| 116 |
+ CompatLazyBg CompatFeature = 0x40 |
|
| 117 |
+ CompatExcludeInode CompatFeature = 0x80 |
|
| 118 |
+ CompatExcludeBitmap CompatFeature = 0x100 |
|
| 119 |
+ CompatSparseSuper2 CompatFeature = 0x200 |
|
| 120 |
+ |
|
| 121 |
+ IncompatCompression IncompatFeature = 0x1 |
|
| 122 |
+ IncompatFiletype IncompatFeature = 0x2 |
|
| 123 |
+ IncompatRecover IncompatFeature = 0x4 |
|
| 124 |
+ IncompatJournalDev IncompatFeature = 0x8 |
|
| 125 |
+ IncompatMetaBg IncompatFeature = 0x10 |
|
| 126 |
+ IncompatExtents IncompatFeature = 0x40 |
|
| 127 |
+ Incompat_64Bit IncompatFeature = 0x80 |
|
| 128 |
+ IncompatMmp IncompatFeature = 0x100 |
|
| 129 |
+ IncompatFlexBg IncompatFeature = 0x200 |
|
| 130 |
+ IncompatEaInode IncompatFeature = 0x400 |
|
| 131 |
+ IncompatDirdata IncompatFeature = 0x1000 |
|
| 132 |
+ IncompatCsumSeed IncompatFeature = 0x2000 |
|
| 133 |
+ IncompatLargedir IncompatFeature = 0x4000 |
|
| 134 |
+ IncompatInlineData IncompatFeature = 0x8000 |
|
| 135 |
+ IncompatEncrypt IncompatFeature = 0x10000 |
|
| 136 |
+ |
|
| 137 |
+ RoCompatSparseSuper RoCompatFeature = 0x1 |
|
| 138 |
+ RoCompatLargeFile RoCompatFeature = 0x2 |
|
| 139 |
+ RoCompatBtreeDir RoCompatFeature = 0x4 |
|
| 140 |
+ RoCompatHugeFile RoCompatFeature = 0x8 |
|
| 141 |
+ RoCompatGdtCsum RoCompatFeature = 0x10 |
|
| 142 |
+ RoCompatDirNlink RoCompatFeature = 0x20 |
|
| 143 |
+ RoCompatExtraIsize RoCompatFeature = 0x40 |
|
| 144 |
+ RoCompatHasSnapshot RoCompatFeature = 0x80 |
|
| 145 |
+ RoCompatQuota RoCompatFeature = 0x100 |
|
| 146 |
+ RoCompatBigalloc RoCompatFeature = 0x200 |
|
| 147 |
+ RoCompatMetadataCsum RoCompatFeature = 0x400 |
|
| 148 |
+ RoCompatReplica RoCompatFeature = 0x800 |
|
| 149 |
+ RoCompatReadonly RoCompatFeature = 0x1000 |
|
| 150 |
+ RoCompatProject RoCompatFeature = 0x2000 |
|
| 151 |
+) |
|
| 152 |
+ |
|
| 153 |
+type BlockGroupFlag uint16 |
|
| 154 |
+ |
|
| 155 |
+const ( |
|
| 156 |
+ BlockGroupInodeUninit BlockGroupFlag = 0x1 |
|
| 157 |
+ BlockGroupBlockUninit BlockGroupFlag = 0x2 |
|
| 158 |
+ BlockGroupInodeZeroed BlockGroupFlag = 0x4 |
|
| 159 |
+) |
|
| 160 |
+ |
|
| 161 |
+type GroupDescriptor struct {
|
|
| 162 |
+ BlockBitmapLow uint32 |
|
| 163 |
+ InodeBitmapLow uint32 |
|
| 164 |
+ InodeTableLow uint32 |
|
| 165 |
+ FreeBlocksCountLow uint16 |
|
| 166 |
+ FreeInodesCountLow uint16 |
|
| 167 |
+ UsedDirsCountLow uint16 |
|
| 168 |
+ Flags BlockGroupFlag |
|
| 169 |
+ ExcludeBitmapLow uint32 |
|
| 170 |
+ BlockBitmapCsumLow uint16 |
|
| 171 |
+ InodeBitmapCsumLow uint16 |
|
| 172 |
+ ItableUnusedLow uint16 |
|
| 173 |
+ Checksum uint16 |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+type GroupDescriptor64 struct {
|
|
| 177 |
+ GroupDescriptor |
|
| 178 |
+ BlockBitmapHigh uint32 |
|
| 179 |
+ InodeBitmapHigh uint32 |
|
| 180 |
+ InodeTableHigh uint32 |
|
| 181 |
+ FreeBlocksCountHigh uint16 |
|
| 182 |
+ FreeInodesCountHigh uint16 |
|
| 183 |
+ UsedDirsCountHigh uint16 |
|
| 184 |
+ ItableUnusedHigh uint16 |
|
| 185 |
+ ExcludeBitmapHigh uint32 |
|
| 186 |
+ BlockBitmapCsumHigh uint16 |
|
| 187 |
+ InodeBitmapCsumHigh uint16 |
|
| 188 |
+ Reserved uint32 |
|
| 189 |
+} |
|
| 190 |
+ |
|
| 191 |
+const ( |
|
| 192 |
+ S_IXOTH = 0x1 |
|
| 193 |
+ S_IWOTH = 0x2 |
|
| 194 |
+ S_IROTH = 0x4 |
|
| 195 |
+ S_IXGRP = 0x8 |
|
| 196 |
+ S_IWGRP = 0x10 |
|
| 197 |
+ S_IRGRP = 0x20 |
|
| 198 |
+ S_IXUSR = 0x40 |
|
| 199 |
+ S_IWUSR = 0x80 |
|
| 200 |
+ S_IRUSR = 0x100 |
|
| 201 |
+ S_ISVTX = 0x200 |
|
| 202 |
+ S_ISGID = 0x400 |
|
| 203 |
+ S_ISUID = 0x800 |
|
| 204 |
+ S_IFIFO = 0x1000 |
|
| 205 |
+ S_IFCHR = 0x2000 |
|
| 206 |
+ S_IFDIR = 0x4000 |
|
| 207 |
+ S_IFBLK = 0x6000 |
|
| 208 |
+ S_IFREG = 0x8000 |
|
| 209 |
+ S_IFLNK = 0xA000 |
|
| 210 |
+ S_IFSOCK = 0xC000 |
|
| 211 |
+ |
|
| 212 |
+ TypeMask uint16 = 0xF000 |
|
| 213 |
+) |
|
| 214 |
+ |
|
| 215 |
+type InodeNumber uint32 |
|
| 216 |
+ |
|
| 217 |
+const ( |
|
| 218 |
+ InodeRoot = 2 |
|
| 219 |
+) |
|
| 220 |
+ |
|
| 221 |
+type Inode struct {
|
|
| 222 |
+ Mode uint16 |
|
| 223 |
+ Uid uint16 |
|
| 224 |
+ SizeLow uint32 |
|
| 225 |
+ Atime uint32 |
|
| 226 |
+ Ctime uint32 |
|
| 227 |
+ Mtime uint32 |
|
| 228 |
+ Dtime uint32 |
|
| 229 |
+ Gid uint16 |
|
| 230 |
+ LinksCount uint16 |
|
| 231 |
+ BlocksLow uint32 |
|
| 232 |
+ Flags InodeFlag |
|
| 233 |
+ Version uint32 |
|
| 234 |
+ Block [60]byte |
|
| 235 |
+ Generation uint32 |
|
| 236 |
+ XattrBlockLow uint32 |
|
| 237 |
+ SizeHigh uint32 |
|
| 238 |
+ ObsoleteFragmentAddr uint32 |
|
| 239 |
+ BlocksHigh uint16 |
|
| 240 |
+ XattrBlockHigh uint16 |
|
| 241 |
+ UidHigh uint16 |
|
| 242 |
+ GidHigh uint16 |
|
| 243 |
+ ChecksumLow uint16 |
|
| 244 |
+ Reserved uint16 |
|
| 245 |
+ ExtraIsize uint16 |
|
| 246 |
+ ChecksumHigh uint16 |
|
| 247 |
+ CtimeExtra uint32 |
|
| 248 |
+ MtimeExtra uint32 |
|
| 249 |
+ AtimeExtra uint32 |
|
| 250 |
+ Crtime uint32 |
|
| 251 |
+ CrtimeExtra uint32 |
|
| 252 |
+ VersionHigh uint32 |
|
| 253 |
+ Projid uint32 |
|
| 254 |
+} |
|
| 255 |
+ |
|
| 256 |
+type InodeFlag uint32 |
|
| 257 |
+ |
|
| 258 |
+const ( |
|
| 259 |
+ InodeFlagSecRm InodeFlag = 0x1 |
|
| 260 |
+ InodeFlagUnRm InodeFlag = 0x2 |
|
| 261 |
+ InodeFlagCompressed InodeFlag = 0x4 |
|
| 262 |
+ InodeFlagSync InodeFlag = 0x8 |
|
| 263 |
+ InodeFlagImmutable InodeFlag = 0x10 |
|
| 264 |
+ InodeFlagAppend InodeFlag = 0x20 |
|
| 265 |
+ InodeFlagNoDump InodeFlag = 0x40 |
|
| 266 |
+ InodeFlagNoAtime InodeFlag = 0x80 |
|
| 267 |
+ InodeFlagDirtyCompressed InodeFlag = 0x100 |
|
| 268 |
+ InodeFlagCompressedClusters InodeFlag = 0x200 |
|
| 269 |
+ InodeFlagNoCompress InodeFlag = 0x400 |
|
| 270 |
+ InodeFlagEncrypted InodeFlag = 0x800 |
|
| 271 |
+ InodeFlagHashedIndex InodeFlag = 0x1000 |
|
| 272 |
+ InodeFlagMagic InodeFlag = 0x2000 |
|
| 273 |
+ InodeFlagJournalData InodeFlag = 0x4000 |
|
| 274 |
+ InodeFlagNoTail InodeFlag = 0x8000 |
|
| 275 |
+ InodeFlagDirSync InodeFlag = 0x10000 |
|
| 276 |
+ InodeFlagTopDir InodeFlag = 0x20000 |
|
| 277 |
+ InodeFlagHugeFile InodeFlag = 0x40000 |
|
| 278 |
+ InodeFlagExtents InodeFlag = 0x80000 |
|
| 279 |
+ InodeFlagEaInode InodeFlag = 0x200000 |
|
| 280 |
+ InodeFlagEOFBlocks InodeFlag = 0x400000 |
|
| 281 |
+ InodeFlagSnapfile InodeFlag = 0x01000000 |
|
| 282 |
+ InodeFlagSnapfileDeleted InodeFlag = 0x04000000 |
|
| 283 |
+ InodeFlagSnapfileShrunk InodeFlag = 0x08000000 |
|
| 284 |
+ InodeFlagInlineData InodeFlag = 0x10000000 |
|
| 285 |
+ InodeFlagProjectIDInherit InodeFlag = 0x20000000 |
|
| 286 |
+ InodeFlagReserved InodeFlag = 0x80000000 |
|
| 287 |
+) |
|
| 288 |
+ |
|
| 289 |
+const ( |
|
| 290 |
+ MaxLinks = 65000 |
|
| 291 |
+) |
|
| 292 |
+ |
|
| 293 |
+type ExtentHeader struct {
|
|
| 294 |
+ Magic uint16 |
|
| 295 |
+ Entries uint16 |
|
| 296 |
+ Max uint16 |
|
| 297 |
+ Depth uint16 |
|
| 298 |
+ Generation uint32 |
|
| 299 |
+} |
|
| 300 |
+ |
|
| 301 |
+const ExtentHeaderMagic uint16 = 0xf30a |
|
| 302 |
+ |
|
| 303 |
+type ExtentIndexNode struct {
|
|
| 304 |
+ Block uint32 |
|
| 305 |
+ LeafLow uint32 |
|
| 306 |
+ LeafHigh uint16 |
|
| 307 |
+ Unused uint16 |
|
| 308 |
+} |
|
| 309 |
+ |
|
| 310 |
+type ExtentLeafNode struct {
|
|
| 311 |
+ Block uint32 |
|
| 312 |
+ Length uint16 |
|
| 313 |
+ StartHigh uint16 |
|
| 314 |
+ StartLow uint32 |
|
| 315 |
+} |
|
| 316 |
+ |
|
| 317 |
+type ExtentTail struct {
|
|
| 318 |
+ Checksum uint32 |
|
| 319 |
+} |
|
| 320 |
+ |
|
| 321 |
+type DirectoryEntry struct {
|
|
| 322 |
+ Inode InodeNumber |
|
| 323 |
+ RecordLength uint16 |
|
| 324 |
+ NameLength uint8 |
|
| 325 |
+ FileType FileType |
|
| 326 |
+ //Name []byte |
|
| 327 |
+} |
|
| 328 |
+ |
|
| 329 |
+type FileType uint8 |
|
| 330 |
+ |
|
| 331 |
+const ( |
|
| 332 |
+ FileTypeUnknown FileType = 0x0 |
|
| 333 |
+ FileTypeRegular FileType = 0x1 |
|
| 334 |
+ FileTypeDirectory FileType = 0x2 |
|
| 335 |
+ FileTypeCharacter FileType = 0x3 |
|
| 336 |
+ FileTypeBlock FileType = 0x4 |
|
| 337 |
+ FileTypeFIFO FileType = 0x5 |
|
| 338 |
+ FileTypeSocket FileType = 0x6 |
|
| 339 |
+ FileTypeSymbolicLink FileType = 0x7 |
|
| 340 |
+) |
|
| 341 |
+ |
|
| 342 |
+type DirectoryEntryTail struct {
|
|
| 343 |
+ ReservedZero1 uint32 |
|
| 344 |
+ RecordLength uint16 |
|
| 345 |
+ ReservedZero2 uint8 |
|
| 346 |
+ FileType uint8 |
|
| 347 |
+ Checksum uint32 |
|
| 348 |
+} |
|
| 349 |
+ |
|
| 350 |
+type DirectoryTreeRoot struct {
|
|
| 351 |
+ Dot DirectoryEntry |
|
| 352 |
+ DotName [4]byte |
|
| 353 |
+ DotDot DirectoryEntry |
|
| 354 |
+ DotDotName [4]byte |
|
| 355 |
+ ReservedZero uint32 |
|
| 356 |
+ HashVersion uint8 |
|
| 357 |
+ InfoLength uint8 |
|
| 358 |
+ IndirectLevels uint8 |
|
| 359 |
+ UnusedFlags uint8 |
|
| 360 |
+ Limit uint16 |
|
| 361 |
+ Count uint16 |
|
| 362 |
+ Block uint32 |
|
| 363 |
+ //Entries []DirectoryTreeEntry |
|
| 364 |
+} |
|
| 365 |
+ |
|
| 366 |
+type DirectoryTreeNode struct {
|
|
| 367 |
+ FakeInode uint32 |
|
| 368 |
+ FakeRecordLength uint16 |
|
| 369 |
+ NameLength uint8 |
|
| 370 |
+ FileType uint8 |
|
| 371 |
+ Limit uint16 |
|
| 372 |
+ Count uint16 |
|
| 373 |
+ Block uint32 |
|
| 374 |
+ //Entries []DirectoryTreeEntry |
|
| 375 |
+} |
|
| 376 |
+ |
|
| 377 |
+type DirectoryTreeEntry struct {
|
|
| 378 |
+ Hash uint32 |
|
| 379 |
+ Block uint32 |
|
| 380 |
+} |
|
| 381 |
+ |
|
| 382 |
+type DirectoryTreeTail struct {
|
|
| 383 |
+ Reserved uint32 |
|
| 384 |
+ Checksum uint32 |
|
| 385 |
+} |
|
| 386 |
+ |
|
| 387 |
+type XAttrInodeBodyHeader struct {
|
|
| 388 |
+ Magic uint32 |
|
| 389 |
+} |
|
| 390 |
+ |
|
| 391 |
+type XAttrHeader struct {
|
|
| 392 |
+ Magic uint32 |
|
| 393 |
+ ReferenceCount uint32 |
|
| 394 |
+ Blocks uint32 |
|
| 395 |
+ Hash uint32 |
|
| 396 |
+ Checksum uint32 |
|
| 397 |
+ Reserved [3]uint32 |
|
| 398 |
+} |
|
| 399 |
+ |
|
| 400 |
+const XAttrHeaderMagic uint32 = 0xea020000 |
|
| 401 |
+ |
|
| 402 |
+type XAttrEntry struct {
|
|
| 403 |
+ NameLength uint8 |
|
| 404 |
+ NameIndex uint8 |
|
| 405 |
+ ValueOffset uint16 |
|
| 406 |
+ ValueInum uint32 |
|
| 407 |
+ ValueSize uint32 |
|
| 408 |
+ Hash uint32 |
|
| 409 |
+ //Name []byte |
|
| 410 |
+} |
| 0 | 411 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,174 @@ |
| 0 |
+package tar2ext4 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "archive/tar" |
|
| 4 |
+ "bufio" |
|
| 5 |
+ "encoding/binary" |
|
| 6 |
+ "io" |
|
| 7 |
+ "path" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/Microsoft/hcsshim/ext4/internal/compactext4" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+type params struct {
|
|
| 14 |
+ convertWhiteout bool |
|
| 15 |
+ appendVhdFooter bool |
|
| 16 |
+ ext4opts []compactext4.Option |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// Option is the type for optional parameters to Convert. |
|
| 20 |
+type Option func(*params) |
|
| 21 |
+ |
|
| 22 |
+// ConvertWhiteout instructs the converter to convert OCI-style whiteouts |
|
| 23 |
+// (beginning with .wh.) to overlay-style whiteouts. |
|
| 24 |
+func ConvertWhiteout(p *params) {
|
|
| 25 |
+ p.convertWhiteout = true |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// AppendVhdFooter instructs the converter to add a fixed VHD footer to the |
|
| 29 |
+// file. |
|
| 30 |
+func AppendVhdFooter(p *params) {
|
|
| 31 |
+ p.appendVhdFooter = true |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// InlineData instructs the converter to write small files into the inode |
|
| 35 |
+// structures directly. This creates smaller images but currently is not |
|
| 36 |
+// compatible with DAX. |
|
| 37 |
+func InlineData(p *params) {
|
|
| 38 |
+ p.ext4opts = append(p.ext4opts, compactext4.InlineData) |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// MaximumDiskSize instructs the writer to limit the disk size to the specified |
|
| 42 |
+// value. This also reserves enough metadata space for the specified disk size. |
|
| 43 |
+// If not provided, then 16GB is the default. |
|
| 44 |
+func MaximumDiskSize(size int64) Option {
|
|
| 45 |
+ return func(p *params) {
|
|
| 46 |
+ p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size)) |
|
| 47 |
+ } |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+const ( |
|
| 51 |
+ whiteoutPrefix = ".wh." |
|
| 52 |
+ opaqueWhiteout = ".wh..wh..opq" |
|
| 53 |
+) |
|
| 54 |
+ |
|
| 55 |
+// Convert writes a compact ext4 file system image that contains the files in the |
|
| 56 |
+// input tar stream. |
|
| 57 |
+func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
|
|
| 58 |
+ var p params |
|
| 59 |
+ for _, opt := range options {
|
|
| 60 |
+ opt(&p) |
|
| 61 |
+ } |
|
| 62 |
+ t := tar.NewReader(bufio.NewReader(r)) |
|
| 63 |
+ fs := compactext4.NewWriter(w, p.ext4opts...) |
|
| 64 |
+ for {
|
|
| 65 |
+ hdr, err := t.Next() |
|
| 66 |
+ if err == io.EOF {
|
|
| 67 |
+ break |
|
| 68 |
+ } |
|
| 69 |
+ if err != nil {
|
|
| 70 |
+ return err |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ if p.convertWhiteout {
|
|
| 74 |
+ dir, name := path.Split(hdr.Name) |
|
| 75 |
+ if strings.HasPrefix(name, whiteoutPrefix) {
|
|
| 76 |
+ if name == opaqueWhiteout {
|
|
| 77 |
+ // Update the directory with the appropriate xattr. |
|
| 78 |
+ f, err := fs.Stat(dir) |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ f.Xattrs["trusted.overlay.opaque"] = []byte("y")
|
|
| 83 |
+ err = fs.Create(dir, f) |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ return err |
|
| 86 |
+ } |
|
| 87 |
+ } else {
|
|
| 88 |
+ // Create an overlay-style whiteout. |
|
| 89 |
+ f := &compactext4.File{
|
|
| 90 |
+ Mode: compactext4.S_IFCHR, |
|
| 91 |
+ Devmajor: 0, |
|
| 92 |
+ Devminor: 0, |
|
| 93 |
+ } |
|
| 94 |
+ err = fs.Create(path.Join(dir, name[len(whiteoutPrefix):]), f) |
|
| 95 |
+ if err != nil {
|
|
| 96 |
+ return err |
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ continue |
|
| 101 |
+ } |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ if hdr.Typeflag == tar.TypeLink {
|
|
| 105 |
+ err = fs.Link(hdr.Linkname, hdr.Name) |
|
| 106 |
+ if err != nil {
|
|
| 107 |
+ return err |
|
| 108 |
+ } |
|
| 109 |
+ } else {
|
|
| 110 |
+ f := &compactext4.File{
|
|
| 111 |
+ Mode: uint16(hdr.Mode), |
|
| 112 |
+ Atime: hdr.AccessTime, |
|
| 113 |
+ Mtime: hdr.ModTime, |
|
| 114 |
+ Ctime: hdr.ChangeTime, |
|
| 115 |
+ Crtime: hdr.ModTime, |
|
| 116 |
+ Size: hdr.Size, |
|
| 117 |
+ Uid: uint32(hdr.Uid), |
|
| 118 |
+ Gid: uint32(hdr.Gid), |
|
| 119 |
+ Linkname: hdr.Linkname, |
|
| 120 |
+ Devmajor: uint32(hdr.Devmajor), |
|
| 121 |
+ Devminor: uint32(hdr.Devminor), |
|
| 122 |
+ Xattrs: make(map[string][]byte), |
|
| 123 |
+ } |
|
| 124 |
+ for key, value := range hdr.PAXRecords {
|
|
| 125 |
+ const xattrPrefix = "SCHILY.xattr." |
|
| 126 |
+ if strings.HasPrefix(key, xattrPrefix) {
|
|
| 127 |
+ f.Xattrs[key[len(xattrPrefix):]] = []byte(value) |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ var typ uint16 |
|
| 132 |
+ switch hdr.Typeflag {
|
|
| 133 |
+ case tar.TypeReg, tar.TypeRegA: |
|
| 134 |
+ typ = compactext4.S_IFREG |
|
| 135 |
+ case tar.TypeSymlink: |
|
| 136 |
+ typ = compactext4.S_IFLNK |
|
| 137 |
+ case tar.TypeChar: |
|
| 138 |
+ typ = compactext4.S_IFCHR |
|
| 139 |
+ case tar.TypeBlock: |
|
| 140 |
+ typ = compactext4.S_IFBLK |
|
| 141 |
+ case tar.TypeDir: |
|
| 142 |
+ typ = compactext4.S_IFDIR |
|
| 143 |
+ case tar.TypeFifo: |
|
| 144 |
+ typ = compactext4.S_IFIFO |
|
| 145 |
+ } |
|
| 146 |
+ f.Mode &= ^compactext4.TypeMask |
|
| 147 |
+ f.Mode |= typ |
|
| 148 |
+ err = fs.Create(hdr.Name, f) |
|
| 149 |
+ if err != nil {
|
|
| 150 |
+ return err |
|
| 151 |
+ } |
|
| 152 |
+ _, err = io.Copy(fs, t) |
|
| 153 |
+ if err != nil {
|
|
| 154 |
+ return err |
|
| 155 |
+ } |
|
| 156 |
+ } |
|
| 157 |
+ } |
|
| 158 |
+ err := fs.Close() |
|
| 159 |
+ if err != nil {
|
|
| 160 |
+ return err |
|
| 161 |
+ } |
|
| 162 |
+ if p.appendVhdFooter {
|
|
| 163 |
+ size, err := w.Seek(0, io.SeekEnd) |
|
| 164 |
+ if err != nil {
|
|
| 165 |
+ return err |
|
| 166 |
+ } |
|
| 167 |
+ err = binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size)) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ return err |
|
| 170 |
+ } |
|
| 171 |
+ } |
|
| 172 |
+ return nil |
|
| 173 |
+} |
| 0 | 174 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,76 @@ |
| 0 |
+package tar2ext4 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "crypto/rand" |
|
| 5 |
+ "encoding/binary" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Constants for the VHD footer |
|
| 9 |
+const ( |
|
| 10 |
+ cookieMagic = "conectix" |
|
| 11 |
+ featureMask = 0x2 |
|
| 12 |
+ fileFormatVersionMagic = 0x00010000 |
|
| 13 |
+ fixedDataOffset = -1 |
|
| 14 |
+ creatorVersionMagic = 0x000a0000 |
|
| 15 |
+ diskTypeFixed = 2 |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+type vhdFooter struct {
|
|
| 19 |
+ Cookie [8]byte |
|
| 20 |
+ Features uint32 |
|
| 21 |
+ FileFormatVersion uint32 |
|
| 22 |
+ DataOffset int64 |
|
| 23 |
+ TimeStamp uint32 |
|
| 24 |
+ CreatorApplication [4]byte |
|
| 25 |
+ CreatorVersion uint32 |
|
| 26 |
+ CreatorHostOS [4]byte |
|
| 27 |
+ OriginalSize int64 |
|
| 28 |
+ CurrentSize int64 |
|
| 29 |
+ DiskGeometry uint32 |
|
| 30 |
+ DiskType uint32 |
|
| 31 |
+ Checksum uint32 |
|
| 32 |
+ UniqueID [16]uint8 |
|
| 33 |
+ SavedState uint8 |
|
| 34 |
+ Reserved [427]uint8 |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func makeFixedVHDFooter(size int64) *vhdFooter {
|
|
| 38 |
+ footer := &vhdFooter{
|
|
| 39 |
+ Features: featureMask, |
|
| 40 |
+ FileFormatVersion: fileFormatVersionMagic, |
|
| 41 |
+ DataOffset: fixedDataOffset, |
|
| 42 |
+ CreatorVersion: creatorVersionMagic, |
|
| 43 |
+ OriginalSize: size, |
|
| 44 |
+ CurrentSize: size, |
|
| 45 |
+ DiskType: diskTypeFixed, |
|
| 46 |
+ UniqueID: generateUUID(), |
|
| 47 |
+ } |
|
| 48 |
+ copy(footer.Cookie[:], cookieMagic) |
|
| 49 |
+ footer.Checksum = calculateCheckSum(footer) |
|
| 50 |
+ return footer |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+func calculateCheckSum(footer *vhdFooter) uint32 {
|
|
| 54 |
+ oldchk := footer.Checksum |
|
| 55 |
+ footer.Checksum = 0 |
|
| 56 |
+ |
|
| 57 |
+ buf := &bytes.Buffer{}
|
|
| 58 |
+ binary.Write(buf, binary.BigEndian, footer) |
|
| 59 |
+ |
|
| 60 |
+ var chk uint32 |
|
| 61 |
+ bufBytes := buf.Bytes() |
|
| 62 |
+ for i := 0; i < len(bufBytes); i++ {
|
|
| 63 |
+ chk += uint32(bufBytes[i]) |
|
| 64 |
+ } |
|
| 65 |
+ footer.Checksum = oldchk |
|
| 66 |
+ return uint32(^chk) |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func generateUUID() [16]byte {
|
|
| 70 |
+ res := [16]byte{}
|
|
| 71 |
+ if _, err := rand.Read(res[:]); err != nil {
|
|
| 72 |
+ panic(err) |
|
| 73 |
+ } |
|
| 74 |
+ return res |
|
| 75 |
+} |