Skip to content

Commit 3eb1b76

Browse files
milantracygvisor-bot
authored andcommitted
Add functionality to deserialize a tar archive to rootfs upper layer.
This is only supported for single-container sandboxes for now. It is meant to be used when rootfs is an overlayfs mount. PiperOrigin-RevId: 805223689
1 parent 5334e5f commit 3eb1b76

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

pkg/sentry/fsimpl/tmpfs/tar.go

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,251 @@ package tmpfs
1616

1717
import (
1818
"archive/tar"
19+
"bytes"
1920
"fmt"
2021
"io"
2122
"os"
23+
"path/filepath"
24+
"strings"
2225
"time"
2326

2427
"gvisor.dev/gvisor/pkg/abi/linux"
2528
"gvisor.dev/gvisor/pkg/context"
2629
"gvisor.dev/gvisor/pkg/log"
30+
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
2731
"gvisor.dev/gvisor/pkg/sentry/vfs"
2832
"gvisor.dev/gvisor/pkg/usermem"
2933
)
3034

35+
// UntarUpperLayer creates the corresponding dentry and its children from the
36+
// given snapshot tar file.
37+
func (fs *filesystem) UntarUpperLayer(ctx context.Context, inFile *os.File) error {
38+
tr := tar.NewReader(inFile)
39+
40+
fs.mu.Lock()
41+
defer fs.mu.Unlock()
42+
43+
return fs.readFromTar(ctx, tr)
44+
}
45+
46+
// readFromTar creates the corresponding dentry and its children from the given
47+
// tar reader.
48+
//
49+
// Preconditions:
50+
// - filesystem.mu must be locked.
51+
func (fs *filesystem) readFromTar(ctx context.Context, tr *tar.Reader) error {
52+
pathToInode := map[string]*inode{}
53+
pathToInode["./"] = fs.root.inode
54+
directoryToHeader := map[string]*tar.Header{}
55+
fileToHeader := map[string]*tar.Header{}
56+
symlinkToHeader := map[string]*tar.Header{}
57+
linkToHeader := map[string]*tar.Header{}
58+
fileToContent := map[string]*bytes.Buffer{}
59+
for {
60+
header, err := tr.Next()
61+
if err != nil {
62+
if err == io.EOF {
63+
break
64+
}
65+
return fmt.Errorf("failed to read tar header: %w", err)
66+
}
67+
68+
switch header.Typeflag {
69+
case tar.TypeDir:
70+
directoryToHeader[header.Name] = header
71+
case tar.TypeReg:
72+
var buffer bytes.Buffer
73+
n, err := io.Copy(&buffer, tr)
74+
if err != nil {
75+
return fmt.Errorf("failed to read file content: %w", err)
76+
}
77+
if n != header.Size {
78+
return fmt.Errorf("failed to read all file content, got %d bytes, want %d", n, header.Size)
79+
}
80+
fileToHeader[header.Name] = header
81+
fileToContent[header.Name] = &buffer
82+
case tar.TypeFifo, tar.TypeBlock, tar.TypeChar:
83+
fileToHeader[header.Name] = header
84+
case tar.TypeSymlink:
85+
symlinkToHeader[header.Name] = header
86+
case tar.TypeLink:
87+
linkToHeader[header.Name] = header
88+
default:
89+
return fmt.Errorf("readfrom unsupported file type %v for %v", header.Typeflag, header.Name)
90+
}
91+
}
92+
// Re-create all directories.
93+
for path, hdr := range directoryToHeader {
94+
if _, err := fs.mkdirFromTar(hdr, pathToInode, directoryToHeader); err != nil {
95+
return fmt.Errorf("failed to make directory %v: %w", path, err)
96+
}
97+
}
98+
// Re-create all regular files, FIFOs, block devices, and character devices.
99+
for path, hdr := range fileToHeader {
100+
if err := fs.mknodFromTar(ctx, hdr, pathToInode, fileToContent); err != nil {
101+
return fmt.Errorf("failed to make file %v: %w", path, err)
102+
}
103+
}
104+
// Re-create all symlinks.
105+
for path, hdr := range symlinkToHeader {
106+
if err := fs.symlinkFromTar(hdr, pathToInode); err != nil {
107+
return fmt.Errorf("failed to make symlink %v: %w", path, err)
108+
}
109+
}
110+
// Re-create all hard links.
111+
// Note that hard links are created after the rest of the supported file types
112+
// since they need to link to existing inodes.
113+
for path, hdr := range linkToHeader {
114+
if err := fs.linkFromTar(hdr, pathToInode); err != nil {
115+
return fmt.Errorf("failed to make hard link %v: %w", path, err)
116+
}
117+
}
118+
return nil
119+
}
120+
121+
// mkdirFromTar recursively creates a directory and its parent directories
122+
// using the provided headers.
123+
func (fs *filesystem) mkdirFromTar(hdr *tar.Header, pathToInode map[string]*inode, pathToHeader map[string]*tar.Header) (*inode, error) {
124+
path := hdr.Name
125+
if ino, ok := pathToInode[hdr.Name]; ok {
126+
return ino, nil
127+
}
128+
dir, name := filepath.Split(strings.TrimSuffix(path, "/"))
129+
parentInode, ok := pathToInode[dir]
130+
if !ok {
131+
parentHdr, ok := pathToHeader[dir]
132+
if !ok {
133+
return nil, fmt.Errorf("failed to find header for %v", dir)
134+
}
135+
var err error
136+
// Recursively create the parent directories.
137+
if parentInode, err = fs.mkdirFromTar(parentHdr, pathToInode, pathToHeader); err != nil {
138+
return nil, err
139+
}
140+
}
141+
if parentInode.nlink.Load() == maxLinks {
142+
return nil, fmt.Errorf("maximum number of links reached for %v", dir)
143+
}
144+
parentDir, ok := parentInode.impl.(*directory)
145+
if !ok {
146+
return nil, fmt.Errorf("parent inode at %v is not a directory", dir)
147+
}
148+
parentDir.inode.incLinksLocked()
149+
childDir := fs.newDirectory(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), linux.FileMode(hdr.Mode), parentDir)
150+
childDir.inode.mtime.Store(hdr.ModTime.UnixNano())
151+
parentDir.insertChildLocked(&childDir.dentry, name)
152+
pathToInode[path] = childDir.dentry.inode
153+
return childDir.dentry.inode, nil
154+
}
155+
156+
// mknodFromTar creates a regular file,FIFO, block device, or character device file using
157+
// the provided header. It also writes the file content to the corresponding regular file if it
158+
// exists.
159+
func (fs *filesystem) mknodFromTar(ctx context.Context, hdr *tar.Header, pathToInode map[string]*inode, pathToContent map[string]*bytes.Buffer) error {
160+
dir, name := filepath.Split(hdr.Name)
161+
parentInode, ok := pathToInode[dir]
162+
if !ok {
163+
return fmt.Errorf("parent directory %v does not exist", dir)
164+
}
165+
parentDir, ok := parentInode.impl.(*directory)
166+
if !ok {
167+
return fmt.Errorf("%v is not a directory", dir)
168+
}
169+
var childInode *inode
170+
switch hdr.Typeflag {
171+
case tar.TypeReg:
172+
childInode = fs.newRegularFile(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), linux.FileMode(hdr.Mode), parentDir)
173+
case tar.TypeFifo:
174+
childInode = fs.newNamedPipe(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), linux.FileMode(hdr.Mode), parentDir)
175+
case tar.TypeBlock:
176+
childInode = fs.newDeviceFileLocked(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), linux.FileMode(hdr.Mode|linux.S_IFBLK), uint32(hdr.Devmajor), uint32(hdr.Devminor), parentDir)
177+
case tar.TypeChar:
178+
childInode = fs.newDeviceFileLocked(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), linux.FileMode(hdr.Mode|linux.S_IFCHR), uint32(hdr.Devmajor), uint32(hdr.Devminor), parentDir)
179+
default:
180+
return fmt.Errorf("mknod unsupported file type %v for %v", hdr.Typeflag, hdr.Name)
181+
}
182+
childInode.mtime.Store(hdr.ModTime.UnixNano())
183+
child := fs.newDentry(childInode)
184+
parentDir.insertChildLocked(child, name)
185+
pathToInode[hdr.Name] = childInode
186+
187+
// Write file contents to the corresponding regular files if they exist.
188+
if buf, ok := pathToContent[hdr.Name]; ok {
189+
if err := fs.writeTo(ctx, hdr.Name, pathToInode, int64(len(buf.Bytes())), buf); err != nil {
190+
return fmt.Errorf("failed to write file content for %v: %w", hdr.Name, err)
191+
}
192+
}
193+
194+
return nil
195+
}
196+
197+
// linkFromTar creates a hard link from the given tar header.
198+
func (fs *filesystem) linkFromTar(hdr *tar.Header, pathToInode map[string]*inode) error {
199+
dir, name := filepath.Split(hdr.Name)
200+
parentInode, ok := pathToInode[dir]
201+
if !ok {
202+
return fmt.Errorf("parent directory %v does not exist", dir)
203+
}
204+
parentDir, ok := parentInode.impl.(*directory)
205+
if !ok {
206+
return fmt.Errorf("%v is not a directory", dir)
207+
}
208+
childInode, ok := pathToInode[hdr.Linkname]
209+
if !ok {
210+
return fmt.Errorf("child inode %v does not exist", hdr.Linkname)
211+
}
212+
if childInode.nlink.Load() == maxLinks {
213+
return fmt.Errorf("maximum number of links reached for %s", hdr.Linkname)
214+
}
215+
childInode.incLinksLocked()
216+
child := fs.newDentry(childInode)
217+
parentDir.insertChildLocked(child, name)
218+
pathToInode[hdr.Name] = child.inode
219+
return nil
220+
}
221+
222+
// symlinkFromTar creates a symlink from the given tar header.
223+
func (fs *filesystem) symlinkFromTar(hdr *tar.Header, pathToInode map[string]*inode) error {
224+
dir, name := filepath.Split(hdr.Name)
225+
parentInode, ok := pathToInode[dir]
226+
if !ok {
227+
return fmt.Errorf("parent directory %v does not exist", dir)
228+
}
229+
if len(hdr.Linkname) >= shortSymlinkLen {
230+
return fmt.Errorf("symlink %v is too long", hdr.Linkname)
231+
}
232+
parentDir, ok := parentInode.impl.(*directory)
233+
if !ok {
234+
return fmt.Errorf("%v is not a directory", dir)
235+
}
236+
child := fs.newDentry(fs.newSymlink(auth.KUID(hdr.Uid), auth.KGID(hdr.Gid), 0777, hdr.Linkname, parentDir))
237+
child.inode.mtime.Store(hdr.ModTime.UnixNano())
238+
parentDir.insertChildLocked(child, name)
239+
pathToInode[hdr.Name] = child.inode
240+
return nil
241+
}
242+
243+
func (fs *filesystem) writeTo(ctx context.Context, path string, pathToInode map[string]*inode, size int64, buf *bytes.Buffer) error {
244+
i, ok := pathToInode[path]
245+
if !ok {
246+
return fmt.Errorf("failed to find inode for %v", path)
247+
}
248+
rf := i.impl.(*regularFile)
249+
rf.inode.mu.Lock()
250+
defer rf.inode.mu.Unlock()
251+
src := usermem.BytesIOSequence(buf.Bytes())
252+
rw := getRegularFileReadWriter(rf, 0, 0)
253+
n, err := src.CopyInTo(ctx, rw)
254+
if err != nil {
255+
return fmt.Errorf("failed to write file content: %w", err)
256+
}
257+
if n != size {
258+
return fmt.Errorf("failed to write all file content to %v, got %d bytes, want %d", path, n, size)
259+
}
260+
putRegularFileReadWriter(rw)
261+
return nil
262+
}
263+
31264
// TarUpperLayer implements vfs.TarSerializer.TarUpperLayer.
32265
func (fs *filesystem) TarUpperLayer(ctx context.Context, outFD *os.File) error {
33266
tw := tar.NewWriter(outFD)
@@ -125,6 +358,8 @@ func (d *dentry) createTarHeader(path string, inoToPath map[uint64]string) (*tar
125358
} else {
126359
header.Typeflag = tar.TypeChar
127360
}
361+
header.Devmajor = int64(impl.major)
362+
header.Devminor = int64(impl.minor)
128363
case *socketFile:
129364
// This is consistent with the behavior of tar(1).
130365
log.Warningf("Skipping socket file %q while generating tar archive", path)

pkg/sentry/fsimpl/tmpfs/tmpfs.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ package tmpfs
3232
import (
3333
"fmt"
3434
"math"
35+
"os"
3536
"strconv"
3637
"strings"
3738
"sync/atomic"
@@ -279,6 +280,16 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
279280
}
280281
}
281282

283+
sourceTarFD := -1
284+
if sourceTar, ok := mopts["source_tar_fd"]; ok {
285+
delete(mopts, "source_tar_fd")
286+
var err error
287+
sourceTarFD, err = strconv.Atoi(sourceTar)
288+
if err != nil {
289+
return nil, nil, fmt.Errorf("failed to parse source_tar option: %w", err)
290+
}
291+
}
292+
282293
if len(mopts) != 0 {
283294
ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: unknown options: %v", mopts)
284295
return nil, nil, linuxerr.EINVAL
@@ -321,6 +332,21 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
321332
return nil, nil, fmt.Errorf("invalid tmpfs root file type: %#o", rootFileType)
322333
}
323334
fs.root = root
335+
336+
if sourceTarFD != -1 {
337+
sourceTar := os.NewFile(uintptr(sourceTarFD), "source_tar")
338+
if sourceTar == nil {
339+
fs.vfsfs.DecRef(ctx)
340+
return nil, nil, fmt.Errorf("source_tar_fd: %d is an invalid file descriptor", sourceTarFD)
341+
}
342+
defer sourceTar.Close()
343+
344+
if err := fs.UntarUpperLayer(ctx, sourceTar); err != nil {
345+
fs.vfsfs.DecRef(ctx)
346+
return nil, nil, err
347+
}
348+
}
349+
324350
return &fs.vfsfs, &root.vfsd, nil
325351
}
326352

0 commit comments

Comments
 (0)