github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/tarutil/tar.go (about) 1 // Copyright 2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tarutil 6 7 import ( 8 "archive/tar" 9 "fmt" 10 "io" 11 "log" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 // passesFilters returns true if the given file passes all filters, false otherwise. 18 func passesFilters(hdr *tar.Header, filters []Filter) bool { 19 for _, filter := range filters { 20 if !filter(hdr) { 21 return false 22 } 23 } 24 return true 25 } 26 27 // applyToArchive applies function f to all files in the given archive 28 func applyToArchive( 29 tarFile io.Reader, f func(tr *tar.Reader, hdr *tar.Header) error) error { 30 tr := tar.NewReader(tarFile) 31 for { 32 hdr, err := tr.Next() 33 if err == io.EOF { 34 break 35 } 36 if err != nil { 37 return err 38 } 39 if err := f(tr, hdr); err != nil { 40 return err 41 } 42 } 43 return nil 44 } 45 46 // ListArchive lists the contents of the given tar archive. 47 func ListArchive(tarFile io.Reader) error { 48 return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error { 49 fmt.Println(hdr.Name) 50 return nil 51 }) 52 } 53 54 // ExtractDir extracts all the contents of the tar file to the given directory. 55 func ExtractDir(tarFile io.Reader, dir string) error { 56 return ExtractDirFilter(tarFile, dir, nil) 57 } 58 59 // ExtractDirFilter extracts a tar file with the given filter. 60 func ExtractDirFilter(tarFile io.Reader, dir string, filters []Filter) error { 61 fi, err := os.Stat(dir) 62 if os.IsNotExist(err) { 63 if err := os.Mkdir(dir, os.ModePerm); err != nil { 64 return fmt.Errorf("could not create directory %s: %v", dir, err) 65 } 66 } else if err != nil || !fi.IsDir() { 67 return fmt.Errorf("could not stat directory %s: %v", dir, err) 68 } 69 70 return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error { 71 if !passesFilters(hdr, filters) { 72 return nil 73 } 74 return createFileInRoot(hdr, tr, dir) 75 }) 76 } 77 78 // CreateTar creates a new tar file with all the contents of a directory. 79 func CreateTar(tarFile io.Writer, files []string) error { 80 return CreateTarFilter(tarFile, files, nil) 81 } 82 83 // CreateTarFilter creates a new tar file of the given files, with the given filter. 84 func CreateTarFilter(tarFile io.Writer, files []string, filters []Filter) error { 85 tw := tar.NewWriter(tarFile) 86 for _, file := range files { 87 err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error { 88 if err != nil { 89 return err 90 } 91 symlink := "" 92 if info.Mode()&os.ModeSymlink != 0 { 93 // TODO: symlinks 94 return fmt.Errorf("symlinks not yet supported: %q", path) 95 } 96 hdr, err := tar.FileInfoHeader(info, symlink) 97 if err != nil { 98 return err 99 } 100 hdr.Name = path 101 if !passesFilters(hdr, filters) { 102 return nil 103 } 104 if err := tw.WriteHeader(hdr); err != nil { 105 return err 106 } 107 switch hdr.Typeflag { 108 case tar.TypeLink, tar.TypeSymlink, tar.TypeChar, tar.TypeBlock, tar.TypeDir, tar.TypeFifo: 109 default: 110 f, err := os.Open(path) 111 if err != nil { 112 return err 113 } 114 if _, err := io.Copy(tw, f); err != nil { 115 f.Close() 116 return err 117 } 118 f.Close() 119 } 120 return nil 121 }) 122 if err != nil { 123 return err 124 } 125 } 126 if err := tw.Close(); err != nil { 127 return err 128 } 129 return nil 130 } 131 132 func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { 133 fi := hdr.FileInfo() 134 path := filepath.Clean(filepath.Join(rootDir, hdr.Name)) 135 if !strings.HasPrefix(path, filepath.Clean(rootDir)) { 136 return fmt.Errorf("file outside root directory: %q", path) 137 } 138 139 switch fi.Mode() & os.ModeType { 140 case os.ModeSymlink: 141 // TODO: support symlinks 142 return fmt.Errorf("symlinks not yet supported: %q", path) 143 144 case os.FileMode(0): 145 f, err := os.Create(path) 146 if err != nil { 147 return err 148 } 149 if _, err := io.Copy(f, r); err != nil { 150 f.Close() 151 return err 152 } 153 if err := f.Close(); err != nil { 154 return err 155 } 156 157 case os.ModeDir: 158 if err := os.MkdirAll(path, fi.Mode()&os.ModePerm); err != nil { 159 return err 160 } 161 162 case os.ModeDevice: 163 // TODO: support block device 164 return fmt.Errorf("block device not yet supported: %q", path) 165 166 case os.ModeCharDevice: 167 // TODO: support char device 168 return fmt.Errorf("char device not yet supported: %q", path) 169 170 default: 171 return fmt.Errorf("%q: Unknown type %#o", path, fi.Mode()&os.ModeType) 172 } 173 174 if err := os.Chmod(path, fi.Mode()&os.ModePerm); err != nil { 175 return fmt.Errorf("error setting mode %#o on %q: %v", 176 fi.Mode()&os.ModePerm, path, err) 177 } 178 // TODO: also set ownership, etc... 179 return nil 180 } 181 182 // Filter is applied to each file while creating or extracting a tar archive. 183 // The filter can modify the tar.Header struct. If the filter returns false, 184 // the file is omitted. 185 type Filter func(hdr *tar.Header) bool 186 187 // NoFilter does not filter or modify any files. 188 func NoFilter(hdr *tar.Header) bool { 189 return true 190 } 191 192 // VerboseFilter prints the name of every file. 193 func VerboseFilter(hdr *tar.Header) bool { 194 fmt.Println(hdr.Name) 195 return true 196 } 197 198 // VerboseLogFilter logs the name of every file. 199 func VerboseLogFilter(hdr *tar.Header) bool { 200 log.Println(hdr.Name) 201 return true 202 } 203 204 // SafeFilter filters out all files which are not regular and not directories. 205 // It also sets appropriate permissions. 206 func SafeFilter(hdr *tar.Header) bool { 207 if hdr.Typeflag == tar.TypeDir { 208 hdr.Mode = 0770 209 return true 210 } 211 if hdr.Typeflag == tar.TypeReg { 212 hdr.Mode = 0660 213 return true 214 } 215 return false 216 }