github.com/lalkh/containerd@v1.4.3/sys/mount_linux.go (about)

     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 sys
    18  
    19  import (
    20  	"runtime"
    21  	"syscall"
    22  	"unsafe"
    23  
    24  	"github.com/containerd/containerd/log"
    25  	"github.com/pkg/errors"
    26  	"golang.org/x/sys/unix"
    27  )
    28  
    29  // FMountat performs mount from the provided directory.
    30  func FMountat(dirfd uintptr, source, target, fstype string, flags uintptr, data string) error {
    31  	var (
    32  		sourceP, targetP, fstypeP, dataP *byte
    33  		pid                              uintptr
    34  		err                              error
    35  		errno, status                    syscall.Errno
    36  	)
    37  
    38  	sourceP, err = syscall.BytePtrFromString(source)
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	targetP, err = syscall.BytePtrFromString(target)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	fstypeP, err = syscall.BytePtrFromString(fstype)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	if data != "" {
    54  		dataP, err = syscall.BytePtrFromString(data)
    55  		if err != nil {
    56  			return err
    57  		}
    58  	}
    59  
    60  	runtime.LockOSThread()
    61  	defer runtime.UnlockOSThread()
    62  
    63  	var pipefds [2]int
    64  	if err := syscall.Pipe2(pipefds[:], syscall.O_CLOEXEC); err != nil {
    65  		return errors.Wrap(err, "failed to open pipe")
    66  	}
    67  
    68  	defer func() {
    69  		// close both ends of the pipe in a deferred function, since open file
    70  		// descriptor table is shared with child
    71  		syscall.Close(pipefds[0])
    72  		syscall.Close(pipefds[1])
    73  	}()
    74  
    75  	pid, errno = forkAndMountat(dirfd,
    76  		uintptr(unsafe.Pointer(sourceP)),
    77  		uintptr(unsafe.Pointer(targetP)),
    78  		uintptr(unsafe.Pointer(fstypeP)),
    79  		flags,
    80  		uintptr(unsafe.Pointer(dataP)),
    81  		pipefds[1],
    82  	)
    83  
    84  	if errno != 0 {
    85  		return errors.Wrap(errno, "failed to fork thread")
    86  	}
    87  
    88  	defer func() {
    89  		_, err := unix.Wait4(int(pid), nil, 0, nil)
    90  		for err == syscall.EINTR {
    91  			_, err = unix.Wait4(int(pid), nil, 0, nil)
    92  		}
    93  
    94  		if err != nil {
    95  			log.L.WithError(err).Debugf("failed to find pid=%d process", pid)
    96  		}
    97  	}()
    98  
    99  	_, _, errno = syscall.RawSyscall(syscall.SYS_READ,
   100  		uintptr(pipefds[0]),
   101  		uintptr(unsafe.Pointer(&status)),
   102  		unsafe.Sizeof(status))
   103  	if errno != 0 {
   104  		return errors.Wrap(errno, "failed to read pipe")
   105  	}
   106  
   107  	if status != 0 {
   108  		return errors.Wrap(status, "failed to mount")
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  // forkAndMountat will fork thread, change working dir and mount.
   115  //
   116  // precondition: the runtime OS thread must be locked.
   117  func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr, pipefd int) (pid uintptr, errno syscall.Errno) {
   118  
   119  	// block signal during clone
   120  	beforeFork()
   121  
   122  	// the cloned thread shares the open file descriptor, but the thread
   123  	// never be reused by runtime.
   124  	pid, _, errno = syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|syscall.CLONE_FILES, 0, 0, 0, 0, 0)
   125  	if errno != 0 || pid != 0 {
   126  		// restore all signals
   127  		afterFork()
   128  		return
   129  	}
   130  
   131  	// restore all signals
   132  	afterForkInChild()
   133  
   134  	// change working dir
   135  	_, _, errno = syscall.RawSyscall(syscall.SYS_FCHDIR, dirfd, 0, 0)
   136  	if errno != 0 {
   137  		goto childerr
   138  	}
   139  	_, _, errno = syscall.RawSyscall6(syscall.SYS_MOUNT, source, target, fstype, flags, data, 0)
   140  
   141  childerr:
   142  	_, _, errno = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipefd), uintptr(unsafe.Pointer(&errno)), unsafe.Sizeof(errno))
   143  	syscall.RawSyscall(syscall.SYS_EXIT, uintptr(errno), 0, 0)
   144  	panic("unreachable")
   145  }