1 Star 0 Fork 1

mysnapcore / mysnapd

forked from tupelo-shen / mysnapd 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
lowlevel.go 17.12 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2017-2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package testutil
import (
"fmt"
"os"
"strings"
"syscall"
"time"
"gopkg.in/check.v1"
"gitee.com/mysnapcore/mysnapd/osutil/mount"
"gitee.com/mysnapcore/mysnapd/osutil/sys"
)
const umountNoFollow = 8
// fakeFileInfo implements os.FileInfo for testing.
//
// Some of the functions panic as we don't expect them to be called.
// Feel free to expand them as necessary.
type fakeFileInfo struct {
name string
mode os.FileMode
}
func (fi *fakeFileInfo) Name() string { return fi.name }
func (*fakeFileInfo) Size() int64 { panic("unexpected call") }
func (fi *fakeFileInfo) Mode() os.FileMode { return fi.mode }
func (*fakeFileInfo) ModTime() time.Time { panic("unexpected call") }
func (fi *fakeFileInfo) IsDir() bool { return fi.Mode().IsDir() }
func (*fakeFileInfo) Sys() interface{} { panic("unexpected call") }
// FakeFileInfo returns a fake object implementing os.FileInfo
func FakeFileInfo(name string, mode os.FileMode) os.FileInfo {
return &fakeFileInfo{name: name, mode: mode}
}
// Convenient FakeFileInfo objects for InsertLstatResult
var (
FileInfoFile = &fakeFileInfo{}
FileInfoDir = &fakeFileInfo{mode: os.ModeDir}
FileInfoSymlink = &fakeFileInfo{mode: os.ModeSymlink}
)
// Formatter for flags passed to open syscall.
//
// Not all flags are handled. Unknown flags cause a panic.
// Please expand the set of recognized flags as tests require.
func formatOpenFlags(flags int) string {
var fl []string
if flags&syscall.O_NOFOLLOW != 0 {
flags ^= syscall.O_NOFOLLOW
fl = append(fl, "O_NOFOLLOW")
}
if flags&syscall.O_CLOEXEC != 0 {
flags ^= syscall.O_CLOEXEC
fl = append(fl, "O_CLOEXEC")
}
if flags&syscall.O_DIRECTORY != 0 {
flags ^= syscall.O_DIRECTORY
fl = append(fl, "O_DIRECTORY")
}
if flags&syscall.O_RDWR != 0 {
flags ^= syscall.O_RDWR
fl = append(fl, "O_RDWR")
}
if flags&syscall.O_CREAT != 0 {
flags ^= syscall.O_CREAT
fl = append(fl, "O_CREAT")
}
if flags&syscall.O_EXCL != 0 {
flags ^= syscall.O_EXCL
fl = append(fl, "O_EXCL")
}
if flags&sys.O_PATH != 0 {
flags ^= sys.O_PATH
fl = append(fl, "O_PATH")
}
if flags != 0 {
panic(fmt.Errorf("unrecognized open flags %d", flags))
}
if len(fl) == 0 {
return "0"
}
return strings.Join(fl, "|")
}
// Formatter for flags passed to mount syscall.
//
// Not all flags are handled. Unknown flags cause a panic.
// Please expand the set of recognized flags as tests require.
func formatMountFlags(flags int) string {
fl, unknown := mount.MountFlagsToOpts(flags)
if unknown != 0 {
panic(fmt.Errorf("unrecognized mount flags %d", unknown))
}
if len(fl) == 0 {
return "0"
}
return strings.Join(fl, "|")
}
// Formatter for flags passed to unmount syscall.
//
// Not all flags are handled. Unknown flags cause a panic.
// Please expand the set of recognized flags as tests require.
func formatUnmountFlags(flags int) string {
fl, unknown := mount.UnmountFlagsToOpts(flags)
if unknown != 0 {
panic(fmt.Errorf("unrecognized unmount flags %d", unknown))
}
if len(fl) == 0 {
return "0"
}
return strings.Join(fl, "|")
}
// CallResultError describes a system call and the corresponding result or error.
//
// The field names stand for Call, Result and Error respectively. They are
// abbreviated due to the nature of their use (in large quantity).
type CallResultError struct {
C string
R interface{}
E error
}
// SyscallRecorder stores which system calls were invoked.
//
// The recorder supports a small set of features useful for testing: injecting
// failures, returning pre-arranged test data, allocation, tracking and
// verification of file descriptors.
type SyscallRecorder struct {
// History of all the system calls made.
rcalls []CallResultError
// Error function for a given system call.
errors map[string]func() error
// pre-arranged result of lstat, fstat and readdir calls.
osLstats map[string]os.FileInfo
sysLstats map[string]syscall.Stat_t
fstats map[string]syscall.Stat_t
fstatfses map[string]func() syscall.Statfs_t
readdirs map[string][]os.FileInfo
readlinkats map[string]string
// allocated file descriptors
fds map[int]string
}
// InsertFault makes given subsequent call to return the specified error.
//
// If one error is provided then the call will reliably fail that way.
// If multiple errors are given then they will be used on subsequent calls
// until the errors finally run out and the call succeeds.
func (sys *SyscallRecorder) InsertFault(call string, errors ...error) {
if sys.errors == nil {
sys.errors = make(map[string]func() error)
}
if len(errors) == 1 {
// deterministic error
sys.errors[call] = func() error {
return errors[0]
}
} else {
// error sequence
sys.errors[call] = func() error {
if len(errors) > 0 {
err := errors[0]
errors = errors[1:]
return err
}
return nil
}
}
}
// InsertFaultFunc arranges given function to be called whenever given call is made.
//
// The main purpose is to allow to vary the behavior of a given system call over time.
// The provided function can return an error or nil to indicate success.
func (sys *SyscallRecorder) InsertFaultFunc(call string, fn func() error) {
if sys.errors == nil {
sys.errors = make(map[string]func() error)
}
sys.errors[call] = fn
}
// Calls returns the sequence of mocked calls that have been made.
func (sys *SyscallRecorder) Calls() []string {
if len(sys.rcalls) == 0 {
return nil
}
calls := make([]string, 0, len(sys.rcalls))
for _, rc := range sys.rcalls {
calls = append(calls, rc.C)
}
return calls
}
// RCalls returns the sequence of mocked calls that have been made along with their results.
func (sys *SyscallRecorder) RCalls() []CallResultError {
return sys.rcalls
}
// rcall remembers that a given call has occurred and returns a pre-arranged error or value, if any
func (sys *SyscallRecorder) rcall(call string, resultFn func(call string) (interface{}, error)) (val interface{}, err error) {
if errorFn := sys.errors[call]; errorFn != nil {
err = errorFn()
}
if err == nil && resultFn != nil {
val, err = resultFn(call)
}
if err != nil {
sys.rcalls = append(sys.rcalls, CallResultError{C: call, E: err})
} else {
sys.rcalls = append(sys.rcalls, CallResultError{C: call, R: val})
}
return val, err
}
// allocFd assigns a file descriptor to a given operation.
func (sys *SyscallRecorder) allocFd(name string) int {
if sys.fds == nil {
sys.fds = make(map[int]string)
}
// Use 3 as the lowest number for tests to look more plausible.
for i := 3; i < 100; i++ {
if _, ok := sys.fds[i]; !ok {
sys.fds[i] = name
return i
}
}
panic("cannot find unused file descriptor")
}
// freeFd closes an open file descriptor.
func (sys *SyscallRecorder) freeFd(fd int) error {
if _, ok := sys.fds[fd]; !ok {
return fmt.Errorf("attempting to close a closed file descriptor %d", fd)
}
delete(sys.fds, fd)
return nil
}
// StrayDescriptorsError returns an error if any descriptor is left unclosed.
func (sys *SyscallRecorder) StrayDescriptorsError() error {
for fd, name := range sys.fds {
return fmt.Errorf("unclosed file descriptor %d (%s)", fd, name)
}
return nil
}
// CheckForStrayDescriptors ensures that all fake file descriptors are closed.
func (sys *SyscallRecorder) CheckForStrayDescriptors(c *check.C) {
c.Assert(sys.StrayDescriptorsError(), check.IsNil)
}
// Open is a fake implementation of syscall.Open
func (sys *SyscallRecorder) Open(path string, flags int, mode uint32) (int, error) {
call := fmt.Sprintf("open %q %s %#o", path, formatOpenFlags(flags), mode)
fd, err := sys.rcall(call, func(call string) (interface{}, error) {
return sys.allocFd(call), nil
})
if err != nil {
return -1, err
}
return fd.(int), nil
}
// Openat is a fake implementation of syscall.Openat
func (sys *SyscallRecorder) Openat(dirfd int, path string, flags int, mode uint32) (int, error) {
call := fmt.Sprintf("openat %d %q %s %#o", dirfd, path, formatOpenFlags(flags), mode)
fd, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[dirfd]; !ok {
return -1, fmt.Errorf("attempting to openat with an invalid file descriptor %d", dirfd)
}
return sys.allocFd(call), nil
})
if err != nil {
return -1, err
}
return fd.(int), nil
}
// Close is a fake implementation of syscall.Close
func (sys *SyscallRecorder) Close(fd int) error {
call := fmt.Sprintf("close %d", fd)
_, err := sys.rcall(call, func(call string) (interface{}, error) {
return nil, sys.freeFd(fd)
})
return err
}
// Fchown is a fake implementation of syscall.Fchown
func (sys *SyscallRecorder) Fchown(fd int, uid sys.UserID, gid sys.GroupID) error {
call := fmt.Sprintf("fchown %d %d %d", fd, uid, gid)
_, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[fd]; !ok {
return nil, fmt.Errorf("attempting to fchown an invalid file descriptor %d", fd)
}
return nil, nil
})
return err
}
// Mkdirat is a fake implementation of syscall.Mkdirat
func (sys *SyscallRecorder) Mkdirat(dirfd int, path string, mode uint32) error {
call := fmt.Sprintf("mkdirat %d %q %#o", dirfd, path, mode)
_, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[dirfd]; !ok {
return nil, fmt.Errorf("attempting to mkdirat with an invalid file descriptor %d", dirfd)
}
return nil, nil
})
return err
}
// Mount is a fake implementation of syscall.Mount
func (sys *SyscallRecorder) Mount(source string, target string, fstype string, flags uintptr, data string) error {
call := fmt.Sprintf("mount %q %q %q %s %q", source, target, fstype, formatMountFlags(int(flags)), data)
_, err := sys.rcall(call, nil)
return err
}
// Unmount is a fake implementation of syscall.Unmount
func (sys *SyscallRecorder) Unmount(target string, flags int) error {
call := fmt.Sprintf("unmount %q %s", target, formatUnmountFlags(flags))
_, err := sys.rcall(call, nil)
return err
}
// InsertOsLstatResult makes given subsequent call to OsLstat return the specified fake file info.
func (sys *SyscallRecorder) InsertOsLstatResult(call string, fi os.FileInfo) {
if sys.osLstats == nil {
sys.osLstats = make(map[string]os.FileInfo)
}
sys.osLstats[call] = fi
}
// InsertSysLstatResult makes given subsequent call to SysLstat return the specified fake file info.
func (sys *SyscallRecorder) InsertSysLstatResult(call string, sb syscall.Stat_t) {
if sys.sysLstats == nil {
sys.sysLstats = make(map[string]syscall.Stat_t)
}
sys.sysLstats[call] = sb
}
// OsLstat is a fake implementation of os.Lstat
func (sys *SyscallRecorder) OsLstat(name string) (os.FileInfo, error) {
// NOTE the syscall.Lstat uses a different signature `lstat %q <ptr>`.
call := fmt.Sprintf("lstat %q", name)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if fi, ok := sys.osLstats[call]; ok {
return fi, nil
}
panic(fmt.Sprintf("one of InsertOsLstatResult() or InsertFault() for %s must be used", call))
})
if err != nil {
return nil, err
}
return val.(os.FileInfo), err
}
// SysLstat is a fake implementation of syscall.Lstat
func (sys *SyscallRecorder) SysLstat(name string, sb *syscall.Stat_t) error {
// NOTE the os.Lstat uses a different signature `lstat %q`.
call := fmt.Sprintf("lstat %q <ptr>", name)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if buf, ok := sys.sysLstats[call]; ok {
return buf, nil
}
panic(fmt.Sprintf("one of InsertSysLstatResult() or InsertFault() for %s must be used", call))
})
if err == nil && sb != nil {
*sb = val.(syscall.Stat_t)
}
return err
}
// InsertFstatResult makes given subsequent call fstat return the specified stat buffer.
func (sys *SyscallRecorder) InsertFstatResult(call string, buf syscall.Stat_t) {
if sys.fstats == nil {
sys.fstats = make(map[string]syscall.Stat_t)
}
sys.fstats[call] = buf
}
// Fstat is a fake implementation of syscall.Fstat
func (sys *SyscallRecorder) Fstat(fd int, buf *syscall.Stat_t) error {
call := fmt.Sprintf("fstat %d <ptr>", fd)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[fd]; !ok {
return nil, fmt.Errorf("attempting to fstat with an invalid file descriptor %d", fd)
}
if buf, ok := sys.fstats[call]; ok {
return buf, nil
}
panic(fmt.Sprintf("one of InsertFstatResult() or InsertFault() for %s must be used", call))
})
if err == nil && buf != nil {
*buf = val.(syscall.Stat_t)
}
return err
}
// InsertFstatfsResult makes given subsequent call fstatfs return the specified stat buffer.
func (sys *SyscallRecorder) InsertFstatfsResult(call string, bufs ...syscall.Statfs_t) {
if sys.fstatfses == nil {
sys.fstatfses = make(map[string]func() syscall.Statfs_t)
}
if len(bufs) == 0 {
panic("cannot provide zero results to InsertFstatfsResult")
}
if len(bufs) == 1 {
// deterministic behavior
sys.fstatfses[call] = func() syscall.Statfs_t {
return bufs[0]
}
} else {
// sequential results with the last element repeated forever.
sys.fstatfses[call] = func() syscall.Statfs_t {
buf := bufs[0]
if len(bufs) > 1 {
bufs = bufs[1:]
}
return buf
}
}
}
// Fstatfs is a fake implementation of syscall.Fstatfs
func (sys *SyscallRecorder) Fstatfs(fd int, buf *syscall.Statfs_t) error {
call := fmt.Sprintf("fstatfs %d <ptr>", fd)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[fd]; !ok {
return nil, fmt.Errorf("attempting to fstatfs with an invalid file descriptor %d", fd)
}
if bufFn, ok := sys.fstatfses[call]; ok {
return bufFn(), nil
}
panic(fmt.Sprintf("one of InsertFstatfsResult() or InsertFault() for %s must be used", call))
})
if err == nil && buf != nil {
*buf = val.(syscall.Statfs_t)
}
return err
}
// InsertReadDirResult makes given subsequent call readdir return the specified fake file infos.
func (sys *SyscallRecorder) InsertReadDirResult(call string, infos []os.FileInfo) {
if sys.readdirs == nil {
sys.readdirs = make(map[string][]os.FileInfo)
}
sys.readdirs[call] = infos
}
// ReadDir is a fake implementation of os.ReadDir
func (sys *SyscallRecorder) ReadDir(dirname string) ([]os.FileInfo, error) {
call := fmt.Sprintf("readdir %q", dirname)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if fi, ok := sys.readdirs[call]; ok {
return fi, nil
}
panic(fmt.Sprintf("one of InsertReadDirResult() or InsertFault() for %s must be used", call))
})
if err == nil {
return val.([]os.FileInfo), nil
}
return nil, err
}
// Symlink is a fake implementation of syscall.Symlink
func (sys *SyscallRecorder) Symlink(oldname, newname string) error {
call := fmt.Sprintf("symlink %q -> %q", newname, oldname)
_, err := sys.rcall(call, nil)
return err
}
// Symlinkat is a fake implementation of osutil.Symlinkat (syscall.Symlinkat is not exposed)
func (sys *SyscallRecorder) Symlinkat(oldname string, dirfd int, newname string) error {
call := fmt.Sprintf("symlinkat %q %d %q", oldname, dirfd, newname)
_, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[dirfd]; !ok {
return nil, fmt.Errorf("attempting to symlinkat with an invalid file descriptor %d", dirfd)
}
return nil, nil
})
return err
}
// InsertReadlinkatResult makes given subsequent call to readlinkat return the specified oldname.
func (sys *SyscallRecorder) InsertReadlinkatResult(call, oldname string) {
if sys.readlinkats == nil {
sys.readlinkats = make(map[string]string)
}
sys.readlinkats[call] = oldname
}
// Readlinkat is a fake implementation of osutil.Readlinkat (syscall.Readlinkat is not exposed)
func (sys *SyscallRecorder) Readlinkat(dirfd int, path string, buf []byte) (int, error) {
call := fmt.Sprintf("readlinkat %d %q <ptr>", dirfd, path)
val, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[dirfd]; !ok {
return nil, fmt.Errorf("attempting to readlinkat with an invalid file descriptor %d", dirfd)
}
if oldname, ok := sys.readlinkats[call]; ok {
return oldname, nil
}
panic(fmt.Sprintf("one of InsertReadlinkatResult() or InsertFault() for %s must be used", call))
})
if err == nil {
n := copy(buf, val.(string))
return n, nil
}
return 0, err
}
// Remove is a fake implementation of os.Remove
func (sys *SyscallRecorder) Remove(name string) error {
call := fmt.Sprintf("remove %q", name)
_, err := sys.rcall(call, nil)
return err
}
// Fchdir is a fake implementation of syscall.Fchdir
func (sys *SyscallRecorder) Fchdir(fd int) error {
call := fmt.Sprintf("fchdir %d", fd)
_, err := sys.rcall(call, func(call string) (interface{}, error) {
if _, ok := sys.fds[fd]; !ok {
return nil, fmt.Errorf("attempting to fchdir with an invalid file descriptor %d", fd)
}
return nil, nil
})
return err
}
Go
1
https://gitee.com/mysnapcore/mysnapd.git
git@gitee.com:mysnapcore/mysnapd.git
mysnapcore
mysnapd
mysnapd
v0.1.0

搜索帮助