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  }