@@ -16,18 +16,251 @@ package tmpfs
1616
1717import (
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.
32265func (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 )
0 commit comments