Skip to content

Commit 37c9ddb

Browse files
committed
mount: handle loopback mount
If a mount has specified `loop` option, we need to handle it on our own instead of passing it to the kernel. In such case, create a loopback device, attach the mount source to it, and mount the loopback device rather than the mount source. Signed-off-by: Peng Tao <[email protected]>
1 parent e2e40e1 commit 37c9ddb

2 files changed

Lines changed: 199 additions & 7 deletions

File tree

mount/losetup_linux.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
Copyright The containerd Authors.
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 mount
18+
19+
import (
20+
"fmt"
21+
"math/rand"
22+
"os"
23+
"strings"
24+
"syscall"
25+
"time"
26+
"unsafe"
27+
28+
"github.com/pkg/errors"
29+
)
30+
31+
const (
32+
loopControlPath = "/dev/loop-control"
33+
loopDevFormat = "/dev/loop%d"
34+
35+
// According to util-linux/include/loopdev.h
36+
ioctlSetFd = 0x4C00
37+
ioctlClrFd = 0x4C01
38+
ioctlSetStatus64 = 0x4C04
39+
ioctlGetFree = 0x4C82
40+
41+
loFlagsReadonly = 1
42+
//loFlagsUseAops = 2
43+
loFlagsAutoclear = 4
44+
//loFlagsPartScan = 8
45+
loFlagsDirectIO = 16
46+
47+
ebusyString = "device or resource busy"
48+
)
49+
50+
// struct loop_info64 in util-linux/include/loopdev.h
51+
type loopInfo struct {
52+
/*
53+
device uint64
54+
inode uint64
55+
rdevice uint64
56+
offset uint64
57+
sizelimit uint64
58+
number uint32
59+
encryptType uint32
60+
encryptKeySize uint32
61+
*/
62+
_ [13]uint32
63+
flags uint32
64+
fileName [64]byte
65+
/*
66+
cryptName [64]byte
67+
encryptKey [32]byte
68+
init [2]uint64
69+
*/
70+
_ [112]byte
71+
}
72+
73+
func ioctl(fd, req, args uintptr) (uintptr, uintptr, error) {
74+
r1, r2, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, req, args)
75+
if errno != 0 {
76+
return 0, 0, errno
77+
}
78+
79+
return r1, r2, nil
80+
}
81+
82+
func getFreeLoopDev() (uint32, error) {
83+
ctrl, err := os.OpenFile(loopControlPath, os.O_RDWR, 0)
84+
if err != nil {
85+
return 0, errors.Errorf("could not open %v: %v", loopControlPath, err)
86+
}
87+
defer ctrl.Close()
88+
num, _, err := ioctl(ctrl.Fd(), ioctlGetFree, 0)
89+
if err != nil {
90+
return 0, errors.Wrap(err, "could not get free loop device")
91+
}
92+
return uint32(num), nil
93+
}
94+
95+
func setupLoopDev(backingFile, loopDev string, ro bool) (devFile *os.File, err error) {
96+
// 1. Open backing file and loop device
97+
oflags := os.O_RDWR
98+
if ro {
99+
oflags = os.O_RDONLY
100+
}
101+
back, err := os.OpenFile(backingFile, oflags, 0)
102+
if err != nil {
103+
return nil, errors.Errorf("could not open backing file: %v", err)
104+
}
105+
defer back.Close()
106+
107+
loopFile, err := os.OpenFile(loopDev, oflags, 0)
108+
if err != nil {
109+
return nil, errors.Errorf("could not open loop device: %v", err)
110+
}
111+
defer func() {
112+
if err != nil {
113+
loopFile.Close()
114+
}
115+
}()
116+
117+
// 2. Set FD
118+
if _, _, err = ioctl(loopFile.Fd(), ioctlSetFd, back.Fd()); err != nil {
119+
return nil, errors.Errorf("could not set loop fd: %v", err)
120+
}
121+
122+
// 3. Set Info
123+
info := loopInfo{}
124+
copy(info.fileName[:], []byte(backingFile))
125+
// Always set autoclear flag so that the device goes away upon umount.
126+
// Always set direct IO flag to avoid double caching.
127+
info.flags = loFlagsAutoclear | loFlagsDirectIO
128+
if ro {
129+
info.flags |= loFlagsReadonly
130+
}
131+
if _, _, err := ioctl(loopFile.Fd(), ioctlSetStatus64, uintptr(unsafe.Pointer(&info))); err != nil {
132+
// Retry w/o direct IO flag in case kernel does not support it. The downside is that
133+
// it will suffer from double cache problem.
134+
info.flags &= ^(uint32(loFlagsDirectIO))
135+
if _, _, err := ioctl(loopFile.Fd(), ioctlSetStatus64, uintptr(unsafe.Pointer(&info))); err != nil {
136+
ioctl(loopFile.Fd(), ioctlClrFd, 0)
137+
return nil, errors.Errorf("cannot set loop info:%v", err)
138+
}
139+
}
140+
141+
return loopFile, nil
142+
}
143+
144+
// setupLoop looks for (and possibly creates) a free loop device, and
145+
// then attaches backingFile to it.
146+
//
147+
// Upon success, the file handle to the loop device is also returned.
148+
// Caller should take care to close it when done with the loop device
149+
// The loop device file handle keeps loFlagsAutoclear in effect and we
150+
// rely on it to clean up the loop device. If caller closes the file
151+
// handle after mounting the device, kernel will clear the loop device
152+
// after it is umounted. Otherwise the loop device is cleared when the
153+
// file handle is closed.
154+
func setupLoop(backingFile string, ro bool) (string, *os.File, error) {
155+
var loopDev string
156+
157+
for retry := 1; ; retry++ {
158+
num, err := getFreeLoopDev()
159+
if err != nil {
160+
return "", nil, err
161+
}
162+
163+
loopDev = fmt.Sprintf(loopDevFormat, num)
164+
loopFile, err := setupLoopDev(backingFile, loopDev, ro)
165+
if err != nil {
166+
// Per util-linux/sys-utils/losetup.c:create_loop(),
167+
// free loop device can race and we end up failing
168+
// with EBUSY when trying to set it up.
169+
if strings.Contains(err.Error(), ebusyString) {
170+
// Fallback a bit to avoid live lock
171+
time.Sleep(time.Millisecond * time.Duration(rand.Intn(retry*10)))
172+
continue
173+
}
174+
return "", nil, err
175+
}
176+
return loopDev, loopFile, nil
177+
}
178+
}

mount/mount_linux.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func init() {
4242
//
4343
// If m.Type starts with "fuse." or "fuse3.", "mount.fuse" or "mount.fuse3"
4444
// helper binary is called.
45-
func (m *Mount) Mount(target string) error {
45+
func (m *Mount) Mount(target string) (err error) {
4646
for _, helperBinary := range allowedHelperBinaries {
4747
// helperBinary = "mount.fuse", typePrefix = "fuse."
4848
typePrefix := strings.TrimPrefix(helperBinary, "mount.") + "."
@@ -62,7 +62,7 @@ func (m *Mount) Mount(target string) error {
6262
chdir, options = compactLowerdirOption(options)
6363
}
6464

65-
flags, data := parseMountOptions(options)
65+
flags, data, losetup := parseMountOptions(options)
6666
if len(data) > pagesize {
6767
return errors.Errorf("mount options is too long")
6868
}
@@ -77,7 +77,17 @@ func (m *Mount) Mount(target string) error {
7777
if flags&unix.MS_REMOUNT == 0 || data != "" {
7878
// Initial call applying all non-propagation flags for mount
7979
// or remount with changed data
80-
if err := mountAt(chdir, m.Source, target, m.Type, uintptr(oflags), data); err != nil {
80+
source := m.Source
81+
if losetup {
82+
dev, devFile, err := setupLoop(m.Source, oflags&unix.MS_RDONLY == unix.MS_RDONLY)
83+
if err != nil {
84+
return err
85+
}
86+
defer devFile.Close()
87+
// Mount the loop device instead
88+
source = dev
89+
}
90+
if err := mountAt(chdir, source, target, m.Type, uintptr(oflags|unix.MS_MGC_VAL), data); err != nil {
8191
return err
8292
}
8393
}
@@ -175,11 +185,13 @@ func UnmountAll(mount string, flags int) error {
175185

176186
// parseMountOptions takes fstab style mount options and parses them for
177187
// use with a standard mount() syscall
178-
func parseMountOptions(options []string) (int, string) {
188+
func parseMountOptions(options []string) (int, string, bool) {
179189
var (
180-
flag int
181-
data []string
190+
flag int
191+
losetup bool
192+
data []string
182193
)
194+
loopOpt := "loop"
183195
flags := map[string]struct {
184196
clear bool
185197
flag int
@@ -220,11 +232,13 @@ func parseMountOptions(options []string) (int, string) {
220232
} else {
221233
flag |= f.flag
222234
}
235+
} else if o == loopOpt {
236+
losetup = true
223237
} else {
224238
data = append(data, o)
225239
}
226240
}
227-
return flag, strings.Join(data, ",")
241+
return flag, strings.Join(data, ","), losetup
228242
}
229243

230244
// compactLowerdirOption updates overlay lowdir option and returns the common

0 commit comments

Comments
 (0)