github.com/intel/goresctrl@v0.5.0/pkg/cgroups/fsimock.go (about) 1 // Copyright 2021 Intel Corporation. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This module implements a mock filesystem that can be used as a 16 // replacement for the native filesystem interface (fsi). 17 18 package cgroups 19 20 import ( 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 ) 28 29 type fsMock struct { 30 files map[string]*mockFile // filesystem contents 31 } 32 33 type mockFile struct { 34 // User-defined file properties 35 data []byte // contents of the file 36 37 // User/fsimock-defined properties 38 info *mockFileInfo 39 40 // File-specific user-overrides for the default file behavior 41 open func(string) (fileIface, error) 42 read func([]byte) (int, error) 43 write func([]byte) (int, error) 44 45 // fsimock-defined properties 46 fs *fsMock 47 filename string 48 handle *mockFileHandle 49 writeHistory [][]byte 50 } 51 52 type mockFileHandle struct { 53 pos int 54 } 55 56 type mockFileInfo struct { 57 mode os.FileMode 58 name string 59 mf *mockFile 60 } 61 62 func NewFsiMock(files map[string]mockFile) fsiIface { 63 mfs := fsMock{} 64 mfs.files = map[string]*mockFile{} 65 for filename, usermf := range files { 66 mf := usermf 67 if mf.info == nil { 68 mf.info = &mockFileInfo{} 69 } 70 if mf.info.name == "" { 71 mf.info.name = filepath.Base(filename) 72 } 73 mf.filename = filename 74 mf.info.mf = &mf 75 mf.fs = &mfs 76 mfs.files[filename] = &mf 77 } 78 return &mfs 79 } 80 81 func (mfs fsMock) OpenFile(name string, flag int, perm os.FileMode) (fileIface, error) { 82 fsmockLog("OpenFile(%q, %d, %d)", name, flag, perm) 83 if mf, ok := mfs.files[name]; ok { 84 mf.handle = &mockFileHandle{} 85 if mf.open != nil { 86 return mf.open(name) 87 } 88 return *mf, nil 89 } 90 return nil, fsmockErrorf("%q: file not found", name) 91 } 92 93 func (mfs fsMock) Open(name string) (fileIface, error) { 94 return mfs.OpenFile(name, 0, 0) 95 } 96 97 func (mfs fsMock) Walk(path string, walkFn filepath.WalkFunc) error { 98 dirPath := strings.TrimSuffix(path, "/") 99 info, err := mfs.Lstat(dirPath) 100 if err != nil { 101 err = walkFn(path, nil, err) 102 return err 103 } 104 if !info.IsDir() { 105 return walkFn(path, info, nil) 106 } 107 err = walkFn(path, info, nil) 108 if err != nil { 109 return err 110 } 111 for _, name := range mfs.dirContents(dirPath) { 112 if err = mfs.Walk(dirPath+"/"+name, walkFn); err != nil && err != filepath.SkipDir { 113 return err 114 } 115 } 116 return nil 117 } 118 119 func (mfs fsMock) dirContents(path string) []string { 120 dirPathS := strings.TrimSuffix(path, "/") + "/" 121 contentSet := map[string]struct{}{} 122 for filename := range mfs.files { 123 if !strings.HasPrefix(filename, dirPathS) { 124 continue 125 } 126 relToDirPath := strings.TrimPrefix(filename, dirPathS) 127 names := strings.SplitN(relToDirPath, "/", 2) 128 contentSet[names[0]] = struct{}{} 129 } 130 contents := make([]string, 0, len(contentSet)) 131 for name := range contentSet { 132 contents = append(contents, name) 133 } 134 return contents 135 } 136 137 func (mfs fsMock) Lstat(path string) (os.FileInfo, error) { 138 if mf, ok := mfs.files[path]; ok { 139 return *mf.info, nil 140 } 141 if len(mfs.dirContents(path)) > 0 { 142 return mockFileInfo{ 143 name: filepath.Base(path), 144 mode: os.ModeDir, 145 }, nil 146 } 147 return mockFileInfo{}, fsmockErrorf("%q: file not found", path) 148 } 149 150 func (mfi mockFileInfo) Name() string { 151 return mfi.name 152 } 153 func (mfi mockFileInfo) Size() int64 { 154 if mfi.mf != nil { 155 return int64(len(mfi.mf.data)) 156 } 157 return 0 158 } 159 func (mfi mockFileInfo) Mode() os.FileMode { 160 return mfi.mode 161 } 162 163 func (mfi mockFileInfo) ModTime() time.Time { 164 return time.Time{} 165 } 166 167 func (mfi mockFileInfo) IsDir() bool { 168 return mfi.mode&os.ModeDir != 0 169 } 170 171 func (mfi mockFileInfo) Sys() interface{} { 172 return nil 173 } 174 175 func (mf mockFile) Write(b []byte) (n int, err error) { 176 pos := mf.handle.pos 177 if mf.write != nil { 178 n, err = mf.write(b) 179 if err == nil { 180 mf.fs.files[mf.filename].writeHistory = append(mf.fs.files[mf.filename].writeHistory, b) 181 } 182 } else { 183 newpos := pos + len(b) 184 if newpos > cap(mf.data) { 185 newdata := make([]byte, newpos) 186 copy(newdata, mf.data) 187 mf.data = newdata 188 } 189 copy(mf.data[pos:newpos], b) 190 mf.handle.pos = newpos 191 if f, ok := mf.fs.files[mf.filename]; ok { 192 f.data = mf.data 193 } 194 mf.fs.files[mf.filename].writeHistory = append(mf.fs.files[mf.filename].writeHistory, b) 195 } 196 fsmockLog("{%q, pos=%d}.Write([%d]byte(%q)) = (%d, %v) %q", mf.filename, pos, len(b), string(b), n, err, mf.fs.files[mf.filename].data) 197 return n, err 198 } 199 200 func (mf mockFile) Read(b []byte) (n int, err error) { 201 pos := mf.handle.pos 202 if mf.read != nil { 203 n, err = mf.read(b) 204 } else { 205 n = len(mf.data) - pos 206 err = nil 207 if n <= 0 { 208 err = io.EOF 209 } 210 if n > cap(b) { 211 n = cap(b) 212 } 213 copy(b, mf.data[pos:pos+n]) 214 mf.handle.pos += n 215 } 216 fsmockLog("{%q, pos=%d}.Read([%d]byte) = (%d, %v)\n", mf.filename, pos, len(b), n, err) 217 return 218 } 219 220 func (mf mockFile) Close() error { 221 return nil 222 } 223 224 func fsmockLog(format string, args ...interface{}) { 225 fmt.Printf("fsmock: "+format+"\n", args...) 226 } 227 228 func fsmockErrorf(format string, args ...interface{}) error { 229 return fmt.Errorf("fsmock: "+format, args...) 230 }