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 }