github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/fs/fs.go (about) 1 /* 2 Copyright 2019 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fs 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "os/user" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "sync" 31 32 "github.com/golang/glog" 33 ) 34 35 const ( 36 emulatorUserName = "libvirt-qemu" 37 defaultMountInfoPath = "/proc/self/mountinfo" 38 ) 39 40 // DelimitedReader is an interface for reading a delimeter-separated 41 // data from files. It can be used for reading /sys and /proc 42 // information, for example. 43 type DelimitedReader interface { 44 // ReadString returns next part of data up to (and including it) 45 // the delimeter byte. 46 ReadString(delim byte) (string, error) 47 // Close closes the reader. 48 Close() error 49 } 50 51 // FileSystem defines a filesystem interface interface 52 type FileSystem interface { 53 // Mount mounts the specified source under the target path. 54 // For bind mounts, bind must be true. The bind mounts will 55 // happen in the host mount namespace (that of PID 1). 56 Mount(source string, target string, fstype string, bind bool) error 57 // Unmount unmounts the specified target directory. If detach 58 // is true, MNT_DETACH option is used (disconnect the 59 // filesystem for the new accesses even if it's busy). 60 Unmount(target string, detach bool) error 61 // IsPathAnNs verifies if the path is a mountpoint with nsfs filesystem type. 62 IsPathAnNs(string) bool 63 // ChownForEmulator makes a file or directory owned by the emulator user. 64 ChownForEmulator(filePath string, recursive bool) error 65 // GetDelimitedReader returns a DelimitedReader for the specified path. 66 GetDelimitedReader(path string) (DelimitedReader, error) 67 // WriteFile creates a new file with the specified path or truncates 68 // the existing one, setting the specified permissions and writing 69 // the data to it. Returns an error if any occured 70 // during the operation. 71 WriteFile(path string, data []byte, perm os.FileMode) error 72 } 73 74 type nullFileSystem struct{} 75 76 // NullFileSystem is a fs that's used for testing and does nothing 77 // instead of mounting/unmounting. 78 var NullFileSystem FileSystem = nullFileSystem{} 79 80 func (fs nullFileSystem) Mount(source string, target string, fstype string, bind bool) error { 81 return nil 82 } 83 84 func (fs nullFileSystem) Unmount(target string, detach bool) error { 85 return nil 86 } 87 88 func (fs nullFileSystem) IsPathAnNs(path string) bool { 89 return false 90 } 91 92 func (fs nullFileSystem) ChownForEmulator(filePath string, recursive bool) error { 93 return nil 94 } 95 96 func (fs nullFileSystem) GetDelimitedReader(path string) (DelimitedReader, error) { 97 return nil, errors.New("not implemented") 98 } 99 100 func (fs nullFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { 101 return nil 102 } 103 104 type mountEntry struct { 105 source, fsType string 106 } 107 108 type realFileSystem struct { 109 sync.Mutex 110 mountInfo map[string]mountEntry 111 gotEmulatorUser bool 112 uid, gid int 113 mountInfoPath string 114 } 115 116 // RealFileSystem provides access to the real filesystem. 117 var RealFileSystem FileSystem = &realFileSystem{} 118 119 func (fs *realFileSystem) ensureMountInfo() error { 120 fs.Lock() 121 defer fs.Unlock() 122 if fs.mountInfo != nil { 123 return nil 124 } 125 126 mountInfoPath := fs.mountInfoPath 127 if mountInfoPath == "" { 128 mountInfoPath = defaultMountInfoPath 129 } 130 131 reader, err := fs.GetDelimitedReader(mountInfoPath) 132 if err != nil { 133 return err 134 } 135 defer reader.Close() 136 137 fs.mountInfo = make(map[string]mountEntry) 138 LineReader: 139 for { 140 line, err := reader.ReadString('\n') 141 switch err { 142 case io.EOF: 143 break LineReader 144 case nil: 145 // strip eol 146 line = strings.Trim(line, "\n") 147 148 // split and parse the entries acording to section 3.5 in 149 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt 150 // TODO: whitespaces and control chars in names are encoded as 151 // octal values (e.g. for "x x": "x\040x") what should be expanded 152 // in both mount point source and target 153 parts := strings.Split(line, " ") 154 if len(parts) < 10 { 155 glog.Errorf("bad mountinfo entry: %q", line) 156 } else { 157 fs.mountInfo[parts[4]] = mountEntry{source: parts[9], fsType: parts[8]} 158 } 159 default: 160 return err 161 } 162 } 163 return nil 164 } 165 166 func (fs *realFileSystem) getMountInfo(path string) (mountEntry, bool, error) { 167 if err := fs.ensureMountInfo(); err != nil { 168 return mountEntry{}, false, err 169 } 170 171 fs.Lock() 172 defer fs.Unlock() 173 entry, ok := fs.mountInfo[path] 174 return entry, ok, nil 175 } 176 177 func (fs *realFileSystem) IsPathAnNs(path string) bool { 178 _, err := os.Stat(path) 179 if err != nil { 180 if !os.IsNotExist(err) { 181 glog.Errorf("Can't check if %q exists: %v", path, err) 182 } 183 return false 184 } 185 realpath, err := filepath.EvalSymlinks(path) 186 if err != nil { 187 glog.Errorf("Can't get the real path of %q: %v", path, err) 188 return false 189 } 190 191 entry, isMountPoint, err := fs.getMountInfo(realpath) 192 if err != nil { 193 glog.Errorf("Can't check if %q is a namespace: error getting mount info: %v", path, err) 194 return false 195 } 196 197 return isMountPoint && (entry.fsType == "nsfs" || entry.fsType == "proc") 198 } 199 200 func (fs *realFileSystem) getEmulatorUidGid() (int, int, error) { 201 fs.Lock() 202 defer fs.Unlock() 203 if !fs.gotEmulatorUser { 204 u, err := user.Lookup(emulatorUserName) 205 if err != nil { 206 return 0, 0, fmt.Errorf("can't find user %q: %v", emulatorUserName, err) 207 } 208 fs.uid, err = strconv.Atoi(u.Uid) 209 if err != nil { 210 return 0, 0, fmt.Errorf("bad uid %q for user %q: %v", u.Uid, emulatorUserName, err) 211 } 212 fs.gid, err = strconv.Atoi(u.Gid) 213 if err != nil { 214 return 0, 0, fmt.Errorf("bad gid %q for user %q: %v", u.Gid, emulatorUserName, err) 215 } 216 } 217 return fs.uid, fs.gid, nil 218 } 219 220 func (fs *realFileSystem) ChownForEmulator(filePath string, recursive bool) error { 221 // don't hold the mutex for the duration of chown 222 uid, gid, err := fs.getEmulatorUidGid() 223 if err != nil { 224 return err 225 } 226 227 chown := os.Chown 228 if recursive { 229 chown = chownR 230 } 231 if err := chown(filePath, uid, gid); err != nil { 232 return fmt.Errorf("can't set the owner of %q: %v", filePath, err) 233 } 234 return nil 235 } 236 237 func (fs *realFileSystem) GetDelimitedReader(path string) (DelimitedReader, error) { 238 f, err := os.Open(path) 239 if err != nil { 240 return nil, err 241 } 242 return &delimitedReader{f, bufio.NewReader(f)}, nil 243 } 244 245 func (fs *realFileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { 246 return ioutil.WriteFile(path, data, perm) 247 } 248 249 type delimitedReader struct { 250 *os.File 251 *bufio.Reader 252 } 253 254 var _ DelimitedReader = &delimitedReader{} 255 256 // chownR makes a file or directory owned by the emulator user recursively. 257 func chownR(path string, uid, gid int) error { 258 return filepath.Walk(path, func(name string, info os.FileInfo, err error) error { 259 if err == nil { 260 err = os.Chown(name, uid, gid) 261 if err != nil { 262 glog.Warningf("Failed to change the owner of %q: %v", name, err) 263 } 264 } 265 return err 266 }) 267 }