loopback: separate loop logic from devicemapper
The loopback logic is not technically exclusive to the devicemapper driver. This reorganizes the code such that the loopback code is usable outside of the devicemapper package and driver. Signed-off-by: Vincent Batts <vbatts@redhat.com>
This commit is contained in:
parent
c28b794f70
commit
74e35aace0
7 changed files with 200 additions and 165 deletions
|
@ -47,32 +47,29 @@ const (
|
||||||
|
|
||||||
// List of errors returned when using devicemapper.
|
// List of errors returned when using devicemapper.
|
||||||
var (
|
var (
|
||||||
ErrTaskRun = errors.New("dm_task_run failed")
|
ErrTaskRun = errors.New("dm_task_run failed")
|
||||||
ErrTaskSetName = errors.New("dm_task_set_name failed")
|
ErrTaskSetName = errors.New("dm_task_set_name failed")
|
||||||
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
||||||
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
||||||
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
|
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
|
||||||
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
||||||
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
||||||
ErrTaskGetDeps = errors.New("dm_task_get_deps failed")
|
ErrTaskGetDeps = errors.New("dm_task_get_deps failed")
|
||||||
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
||||||
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
|
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
|
||||||
ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed")
|
ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed")
|
||||||
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
||||||
ErrNilCookie = errors.New("cookie ptr can't be nil")
|
ErrNilCookie = errors.New("cookie ptr can't be nil")
|
||||||
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
ErrGetBlockSize = errors.New("Can't get block size")
|
||||||
ErrGetBlockSize = errors.New("Can't get block size")
|
ErrUdevWait = errors.New("wait on udev cookie failed")
|
||||||
ErrUdevWait = errors.New("wait on udev cookie failed")
|
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
||||||
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
||||||
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
ErrCreateRemoveTask = errors.New("Can't create task of type deviceRemove")
|
||||||
ErrCreateRemoveTask = errors.New("Can't create task of type deviceRemove")
|
ErrRunRemoveDevice = errors.New("running RemoveDevice failed")
|
||||||
ErrRunRemoveDevice = errors.New("running RemoveDevice failed")
|
ErrInvalidAddNode = errors.New("Invalid AddNode type")
|
||||||
ErrInvalidAddNode = errors.New("Invalid AddNode type")
|
ErrBusy = errors.New("Device is Busy")
|
||||||
ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
|
ErrDeviceIDExists = errors.New("Device Id Exists")
|
||||||
ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
|
ErrEnxio = errors.New("No such device or address")
|
||||||
ErrBusy = errors.New("Device is Busy")
|
|
||||||
ErrDeviceIDExists = errors.New("Device Id Exists")
|
|
||||||
ErrEnxio = errors.New("No such device or address")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -257,58 +254,6 @@ func (t *Task) getNextTarget(next unsafe.Pointer) (nextPtr unsafe.Pointer, start
|
||||||
start, length, targetType, params
|
start, length, targetType, params
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
|
|
||||||
loopInfo, err := ioctlLoopGetStatus64(file.Fd())
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("devicemapper: Error get loopback backing file: %s", err)
|
|
||||||
return 0, 0, ErrGetLoopbackBackingFile
|
|
||||||
}
|
|
||||||
return loopInfo.loDevice, loopInfo.loInode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoopbackSetCapacity reloads the size for the loopback device.
|
|
||||||
func LoopbackSetCapacity(file *os.File) error {
|
|
||||||
if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil {
|
|
||||||
logrus.Errorf("devicemapper: Error loopbackSetCapacity: %s", err)
|
|
||||||
return ErrLoopbackSetCapacity
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindLoopDeviceFor returns a loopback device file for the specified file which
|
|
||||||
// is backing file of a loop back device.
|
|
||||||
func FindLoopDeviceFor(file *os.File) *os.File {
|
|
||||||
stat, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
targetInode := stat.Sys().(*syscall.Stat_t).Ino
|
|
||||||
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
|
|
||||||
|
|
||||||
for i := 0; true; i++ {
|
|
||||||
path := fmt.Sprintf("/dev/loop%d", i)
|
|
||||||
|
|
||||||
file, err := os.OpenFile(path, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore all errors until the first not-exist
|
|
||||||
// we want to continue looking for the file
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dev, inode, err := getLoopbackBackingFile(file)
|
|
||||||
if err == nil && dev == targetDevice && inode == targetInode {
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UdevWait waits for any processes that are waiting for udev to complete the specified cookie.
|
// UdevWait waits for any processes that are waiting for udev to complete the specified cookie.
|
||||||
func UdevWait(cookie *uint) error {
|
func UdevWait(cookie *uint) error {
|
||||||
if res := DmUdevWait(*cookie); res != 1 {
|
if res := DmUdevWait(*cookie); res != 1 {
|
||||||
|
|
|
@ -5,17 +5,8 @@ package devicemapper
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -L. -ldevmapper
|
#cgo LDFLAGS: -L. -ldevmapper
|
||||||
#include <libdevmapper.h>
|
#include <libdevmapper.h>
|
||||||
#include <linux/loop.h> // FIXME: present only for defines, maybe we can remove it?
|
|
||||||
#include <linux/fs.h> // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
|
#include <linux/fs.h> // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
|
||||||
|
|
||||||
#ifndef LOOP_CTL_GET_FREE
|
|
||||||
#define LOOP_CTL_GET_FREE 0x4C82
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef LO_FLAGS_PARTSCAN
|
|
||||||
#define LO_FLAGS_PARTSCAN 8
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// FIXME: Can't we find a way to do the logging in pure Go?
|
// FIXME: Can't we find a way to do the logging in pure Go?
|
||||||
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
|
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
|
||||||
|
|
||||||
|
@ -45,44 +36,12 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
cdmTask C.struct_dm_task
|
cdmTask C.struct_dm_task
|
||||||
|
|
||||||
loopInfo64 struct {
|
|
||||||
loDevice uint64 /* ioctl r/o */
|
|
||||||
loInode uint64 /* ioctl r/o */
|
|
||||||
loRdevice uint64 /* ioctl r/o */
|
|
||||||
loOffset uint64
|
|
||||||
loSizelimit uint64 /* bytes, 0 == max available */
|
|
||||||
loNumber uint32 /* ioctl r/o */
|
|
||||||
loEncryptType uint32
|
|
||||||
loEncryptKeySize uint32 /* ioctl w/o */
|
|
||||||
loFlags uint32 /* ioctl r/o */
|
|
||||||
loFileName [LoNameSize]uint8
|
|
||||||
loCryptName [LoNameSize]uint8
|
|
||||||
loEncryptKey [LoKeySize]uint8 /* ioctl w/o */
|
|
||||||
loInit [2]uint64
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IOCTL consts
|
// IOCTL consts
|
||||||
const (
|
const (
|
||||||
BlkGetSize64 = C.BLKGETSIZE64
|
BlkGetSize64 = C.BLKGETSIZE64
|
||||||
BlkDiscard = C.BLKDISCARD
|
BlkDiscard = C.BLKDISCARD
|
||||||
|
|
||||||
LoopSetFd = C.LOOP_SET_FD
|
|
||||||
LoopCtlGetFree = C.LOOP_CTL_GET_FREE
|
|
||||||
LoopGetStatus64 = C.LOOP_GET_STATUS64
|
|
||||||
LoopSetStatus64 = C.LOOP_SET_STATUS64
|
|
||||||
LoopClrFd = C.LOOP_CLR_FD
|
|
||||||
LoopSetCapacity = C.LOOP_SET_CAPACITY
|
|
||||||
)
|
|
||||||
|
|
||||||
// LOOP consts.
|
|
||||||
const (
|
|
||||||
LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
|
|
||||||
LoFlagsReadOnly = C.LO_FLAGS_READ_ONLY
|
|
||||||
LoFlagsPartScan = C.LO_FLAGS_PARTSCAN
|
|
||||||
LoKeySize = C.LO_KEY_SIZE
|
|
||||||
LoNameSize = C.LO_NAME_SIZE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Devicemapper cookie flags.
|
// Devicemapper cookie flags.
|
||||||
|
|
|
@ -7,51 +7,6 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ioctlLoopCtlGetFree(fd uintptr) (int, error) {
|
|
||||||
index, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, LoopCtlGetFree, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int(index), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlLoopSetFd(loopFd, sparseFd uintptr) error {
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetFd, sparseFd); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlLoopSetStatus64(loopFd uintptr, loopInfo *loopInfo64) error {
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlLoopClrFd(loopFd uintptr) error {
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopClrFd, 0); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlLoopGetStatus64(loopFd uintptr) (*loopInfo64, error) {
|
|
||||||
loopInfo := &loopInfo64{}
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return loopInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlLoopSetCapacity(loopFd uintptr, value int) error {
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetCapacity, uintptr(value)); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctlBlkGetSize64(fd uintptr) (int64, error) {
|
func ioctlBlkGetSize64(fd uintptr) (int64, error) {
|
||||||
var size int64
|
var size int64
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 {
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package devicemapper
|
package loopback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -10,6 +11,13 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Loopback related errors
|
||||||
|
var (
|
||||||
|
ErrAttachLoopbackDevice = errors.New("loopback attach failed")
|
||||||
|
ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
|
||||||
|
ErrSetCapacity = errors.New("Unable set loopback capacity")
|
||||||
|
)
|
||||||
|
|
||||||
func stringToLoopName(src string) [LoNameSize]uint8 {
|
func stringToLoopName(src string) [LoNameSize]uint8 {
|
||||||
var dst [LoNameSize]uint8
|
var dst [LoNameSize]uint8
|
||||||
copy(dst[:], src[:])
|
copy(dst[:], src[:])
|
53
loopback/ioctl.go
Normal file
53
loopback/ioctl.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package loopback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ioctlLoopCtlGetFree(fd uintptr) (int, error) {
|
||||||
|
index, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, LoopCtlGetFree, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(index), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlLoopSetFd(loopFd, sparseFd uintptr) error {
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetFd, sparseFd); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlLoopSetStatus64(loopFd uintptr, loopInfo *loopInfo64) error {
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlLoopClrFd(loopFd uintptr) error {
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopClrFd, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlLoopGetStatus64(loopFd uintptr) (*loopInfo64, error) {
|
||||||
|
loopInfo := &loopInfo64{}
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return loopInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlLoopSetCapacity(loopFd uintptr, value int) error {
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetCapacity, uintptr(value)); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
52
loopback/loop_wrapper.go
Normal file
52
loopback/loop_wrapper.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package loopback
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <linux/loop.h> // FIXME: present only for defines, maybe we can remove it?
|
||||||
|
|
||||||
|
#ifndef LOOP_CTL_GET_FREE
|
||||||
|
#define LOOP_CTL_GET_FREE 0x4C82
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LO_FLAGS_PARTSCAN
|
||||||
|
#define LO_FLAGS_PARTSCAN 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type loopInfo64 struct {
|
||||||
|
loDevice uint64 /* ioctl r/o */
|
||||||
|
loInode uint64 /* ioctl r/o */
|
||||||
|
loRdevice uint64 /* ioctl r/o */
|
||||||
|
loOffset uint64
|
||||||
|
loSizelimit uint64 /* bytes, 0 == max available */
|
||||||
|
loNumber uint32 /* ioctl r/o */
|
||||||
|
loEncryptType uint32
|
||||||
|
loEncryptKeySize uint32 /* ioctl w/o */
|
||||||
|
loFlags uint32 /* ioctl r/o */
|
||||||
|
loFileName [LoNameSize]uint8
|
||||||
|
loCryptName [LoNameSize]uint8
|
||||||
|
loEncryptKey [LoKeySize]uint8 /* ioctl w/o */
|
||||||
|
loInit [2]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IOCTL consts
|
||||||
|
const (
|
||||||
|
LoopSetFd = C.LOOP_SET_FD
|
||||||
|
LoopCtlGetFree = C.LOOP_CTL_GET_FREE
|
||||||
|
LoopGetStatus64 = C.LOOP_GET_STATUS64
|
||||||
|
LoopSetStatus64 = C.LOOP_SET_STATUS64
|
||||||
|
LoopClrFd = C.LOOP_CLR_FD
|
||||||
|
LoopSetCapacity = C.LOOP_SET_CAPACITY
|
||||||
|
)
|
||||||
|
|
||||||
|
// LOOP consts.
|
||||||
|
const (
|
||||||
|
LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
|
||||||
|
LoFlagsReadOnly = C.LO_FLAGS_READ_ONLY
|
||||||
|
LoFlagsPartScan = C.LO_FLAGS_PARTSCAN
|
||||||
|
LoKeySize = C.LO_KEY_SIZE
|
||||||
|
LoNameSize = C.LO_NAME_SIZE
|
||||||
|
)
|
63
loopback/loopback.go
Normal file
63
loopback/loopback.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package loopback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
|
||||||
|
loopInfo, err := ioctlLoopGetStatus64(file.Fd())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error get loopback backing file: %s", err)
|
||||||
|
return 0, 0, ErrGetLoopbackBackingFile
|
||||||
|
}
|
||||||
|
return loopInfo.loDevice, loopInfo.loInode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity reloads the size for the loopback device.
|
||||||
|
func SetCapacity(file *os.File) error {
|
||||||
|
if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil {
|
||||||
|
logrus.Errorf("Error loopbackSetCapacity: %s", err)
|
||||||
|
return ErrSetCapacity
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindLoopDeviceFor returns a loopback device file for the specified file which
|
||||||
|
// is backing file of a loop back device.
|
||||||
|
func FindLoopDeviceFor(file *os.File) *os.File {
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
targetInode := stat.Sys().(*syscall.Stat_t).Ino
|
||||||
|
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
|
||||||
|
|
||||||
|
for i := 0; true; i++ {
|
||||||
|
path := fmt.Sprintf("/dev/loop%d", i)
|
||||||
|
|
||||||
|
file, err := os.OpenFile(path, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore all errors until the first not-exist
|
||||||
|
// we want to continue looking for the file
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, inode, err := getLoopbackBackingFile(file)
|
||||||
|
if err == nil && dev == targetDevice && inode == targetInode {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue