github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage0/gc.go (about)

     1  // Copyright 2015 The rkt Authors
     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  //+build linux
    16  
    17  package stage0
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  	"syscall"
    31  
    32  	"github.com/appc/spec/schema/types"
    33  	"github.com/hashicorp/errwrap"
    34  )
    35  
    36  // GC enters the pod by fork/exec()ing the stage1's /gc similar to /init.
    37  // /gc can expect to have its CWD set to the pod root.
    38  // stage1Path is the path of the stage1 rootfs
    39  func GC(pdir string, uuid *types.UUID, stage1Path string) error {
    40  	err := unregisterPod(pdir, uuid)
    41  	if err != nil {
    42  		// Probably not worth abandoning the rest
    43  		log.PrintE("warning: could not unregister pod with metadata service", err)
    44  	}
    45  
    46  	ep, err := getStage1Entrypoint(pdir, gcEntrypoint)
    47  	if err != nil {
    48  		return errwrap.Wrap(errors.New("error determining 'gc' entrypoint"), err)
    49  	}
    50  
    51  	args := []string{filepath.Join(stage1Path, ep)}
    52  	if debugEnabled {
    53  		args = append(args, "--debug")
    54  	}
    55  	args = append(args, uuid.String())
    56  
    57  	c := exec.Cmd{
    58  		Path:   args[0],
    59  		Args:   args,
    60  		Stderr: os.Stderr,
    61  		Dir:    pdir,
    62  	}
    63  	return c.Run()
    64  }
    65  
    66  type mount struct {
    67  	id         int
    68  	parentID   int
    69  	mountPoint string
    70  	opt        map[string]struct{}
    71  }
    72  
    73  type mounts []*mount
    74  
    75  // getMountDepth determines and returns the number of ancestors of the mount at index i
    76  func (m mounts) getMountDepth(i int) int {
    77  	ancestorCount := 0
    78  	current := m[i]
    79  	for found := true; found; {
    80  		found = false
    81  		for _, mnt := range m {
    82  			if mnt.id == current.parentID {
    83  				ancestorCount += 1
    84  				current = mnt
    85  				found = true
    86  				break
    87  			}
    88  		}
    89  	}
    90  	return ancestorCount
    91  }
    92  
    93  // Less ensures that mounts are sorted in an order we can unmount; descendant before ancestor.
    94  // The requirement of transitivity for Less has to be fulfilled otherwise the sort algorithm will fail.
    95  func (m mounts) Less(i, j int) (result bool) { return m.getMountDepth(i) >= m.getMountDepth(j) }
    96  func (m mounts) Len() int                    { return len(m) }
    97  func (m mounts) Swap(i, j int)               { m[i], m[j] = m[j], m[i] }
    98  
    99  // getMountsForPrefix parses mi (/proc/PID/mountinfo) and returns mounts for path prefix
   100  func getMountsForPrefix(path string, mi io.Reader) (mounts, error) {
   101  	var podMounts mounts
   102  	sc := bufio.NewScanner(mi)
   103  	var (
   104  		mountID    int
   105  		parentID   int
   106  		mountPoint string
   107  		opt        map[string]struct{}
   108  	)
   109  
   110  	for sc.Scan() {
   111  		line := sc.Text()
   112  		lineResult := strings.Split(line, " ")
   113  		if len(lineResult) < 7 {
   114  			return nil, fmt.Errorf("Not enough fields from line %q: %+v", line, lineResult)
   115  		}
   116  
   117  		opt = map[string]struct{}{}
   118  		for i, s := range lineResult {
   119  			if s == "-" {
   120  				break
   121  			}
   122  			var err error
   123  			switch i {
   124  			case 0:
   125  				mountID, err = strconv.Atoi(s)
   126  			case 1:
   127  				parentID, err = strconv.Atoi(s)
   128  			case 2, 3:
   129  			case 4:
   130  				mountPoint = s
   131  			default:
   132  				split := strings.Split(s, ":")
   133  				switch len(split) {
   134  				case 1:
   135  					// we ignore modes like rw, relatime, etc.
   136  				case 2:
   137  					opt[split[0]] = struct{}{}
   138  				default:
   139  					err = fmt.Errorf("found unexpected key:value field with more than two colons: %s", s)
   140  				}
   141  			}
   142  			if err != nil {
   143  				return nil, errwrap.Wrap(fmt.Errorf("could not parse mountinfo line %q", line), err)
   144  			}
   145  		}
   146  
   147  		if strings.Contains(mountPoint, path) {
   148  			mnt := &mount{
   149  				id:         mountID,
   150  				parentID:   parentID,
   151  				mountPoint: mountPoint,
   152  				opt:        opt,
   153  			}
   154  			podMounts = append(podMounts, mnt)
   155  		}
   156  	}
   157  	if err := sc.Err(); err != nil {
   158  		return nil, errwrap.Wrap(errors.New("problem parsing mountinfo"), err)
   159  	}
   160  	sort.Sort(podMounts)
   161  	return podMounts, nil
   162  }
   163  
   164  func needsRemountPrivate(mnt *mount) bool {
   165  	for _, key := range []string{
   166  		"shared",
   167  		"master",
   168  	} {
   169  		if _, needsRemount := mnt.opt[key]; needsRemount {
   170  			return true
   171  		}
   172  	}
   173  	return false
   174  }
   175  
   176  // MountGC removes mounts from pods that couldn't be GCed cleanly.
   177  func MountGC(path, uuid string) error {
   178  	mi, err := os.Open("/proc/self/mountinfo")
   179  	if err != nil {
   180  		return err
   181  	}
   182  	defer mi.Close()
   183  
   184  	mnts, err := getMountsForPrefix(path, mi)
   185  	if err != nil {
   186  		return errwrap.Wrap(fmt.Errorf("error getting mounts for pod %s from mountinfo", uuid), err)
   187  	}
   188  
   189  	for i := len(mnts) - 1; i >= 0; i -= 1 {
   190  		mnt := mnts[i]
   191  		if needsRemountPrivate(mnt) {
   192  			if err := syscall.Mount("", mnt.mountPoint, "", syscall.MS_PRIVATE, ""); err != nil {
   193  				return errwrap.Wrap(fmt.Errorf("could not remount at %v", mnt.mountPoint), err)
   194  			}
   195  		}
   196  	}
   197  
   198  	for _, mnt := range mnts {
   199  		if err := syscall.Unmount(mnt.mountPoint, 0); err != nil {
   200  			if err != syscall.ENOENT && err != syscall.EINVAL {
   201  				return errwrap.Wrap(fmt.Errorf("could not unmount %v", mnt.mountPoint), err)
   202  			}
   203  		}
   204  	}
   205  	return nil
   206  }