github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage1_fly/run/main.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 package main 16 17 import ( 18 "bufio" 19 "errors" 20 "flag" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 "syscall" 27 28 "github.com/appc/spec/schema" 29 "github.com/appc/spec/schema/types" 30 "github.com/hashicorp/errwrap" 31 32 "github.com/coreos/rkt/common" 33 rktlog "github.com/coreos/rkt/pkg/log" 34 "github.com/coreos/rkt/pkg/sys" 35 stage1common "github.com/coreos/rkt/stage1/common" 36 stage1commontypes "github.com/coreos/rkt/stage1/common/types" 37 ) 38 39 const ( 40 flavor = "fly" 41 ) 42 43 type flyMount struct { 44 HostPath string 45 TargetPrefixPath string 46 RelTargetPath string 47 Fs string 48 Flags uintptr 49 } 50 51 type volumeMountTuple struct { 52 V types.Volume 53 M schema.Mount 54 } 55 56 var ( 57 debug bool 58 59 discardNetlist common.NetList 60 discardBool bool 61 discardString string 62 63 log *rktlog.Logger 64 diag *rktlog.Logger 65 ) 66 67 func getHostMounts() (map[string]struct{}, error) { 68 hostMounts := map[string]struct{}{} 69 70 mi, err := os.Open("/proc/self/mountinfo") 71 if err != nil { 72 return nil, err 73 } 74 defer mi.Close() 75 76 sc := bufio.NewScanner(mi) 77 for sc.Scan() { 78 var ( 79 discard string 80 mountPoint string 81 ) 82 83 _, err := fmt.Sscanf(sc.Text(), 84 "%s %s %s %s %s", 85 &discard, &discard, &discard, &discard, &mountPoint, 86 ) 87 if err != nil { 88 return nil, err 89 } 90 91 hostMounts[mountPoint] = struct{}{} 92 } 93 if sc.Err() != nil { 94 return nil, errwrap.Wrap(errors.New("problem parsing mountinfo"), sc.Err()) 95 } 96 return hostMounts, nil 97 } 98 99 func init() { 100 flag.BoolVar(&debug, "debug", false, "Run in debug mode") 101 102 // The following flags need to be supported by stage1 according to 103 // https://github.com/coreos/rkt/blob/master/Documentation/devel/stage1-implementors-guide.md 104 // TODO: either implement functionality or give not implemented warnings 105 flag.Var(&discardNetlist, "net", "Setup networking") 106 flag.BoolVar(&discardBool, "interactive", true, "The pod is interactive") 107 flag.StringVar(&discardString, "mds-token", "", "MDS auth token") 108 flag.StringVar(&discardString, "local-config", common.DefaultLocalConfigDir, "Local config path") 109 } 110 111 func evaluateMounts(rfs string, app string, p *stage1commontypes.Pod) ([]flyMount, error) { 112 imApp := p.Images[app].App 113 namedVolumeMounts := map[types.ACName]volumeMountTuple{} 114 115 for _, m := range p.Manifest.Apps[0].Mounts { 116 _, exists := namedVolumeMounts[m.Volume] 117 if exists { 118 return nil, fmt.Errorf("duplicate mount given: %q", m.Volume) 119 } 120 namedVolumeMounts[m.Volume] = volumeMountTuple{M: m} 121 diag.Printf("adding %+v", namedVolumeMounts[m.Volume]) 122 } 123 124 // Merge command-line Mounts with ImageManifest's MountPoints 125 for _, mp := range imApp.MountPoints { 126 tuple, exists := namedVolumeMounts[mp.Name] 127 switch { 128 case exists && tuple.M.Path != mp.Path: 129 return nil, fmt.Errorf("conflicting path information from mount and mountpoint %q", mp.Name) 130 case !exists: 131 namedVolumeMounts[mp.Name] = volumeMountTuple{M: schema.Mount{Volume: mp.Name, Path: mp.Path}} 132 diag.Printf("adding %+v", namedVolumeMounts[mp.Name]) 133 } 134 } 135 136 // Insert the command-line Volumes 137 for _, v := range p.Manifest.Volumes { 138 // Check if we have a mount for this volume 139 tuple, exists := namedVolumeMounts[v.Name] 140 if !exists { 141 return nil, fmt.Errorf("missing mount for volume %q", v.Name) 142 } else if tuple.M.Volume != v.Name { 143 // assertion regarding the implementation, should never happen 144 return nil, fmt.Errorf("mismatched volume:mount pair: %q != %q", v.Name, tuple.M.Volume) 145 } 146 namedVolumeMounts[v.Name] = volumeMountTuple{V: v, M: tuple.M} 147 diag.Printf("adding %+v", namedVolumeMounts[v.Name]) 148 } 149 150 // Merge command-line Volumes with ImageManifest's MountPoints 151 for _, mp := range imApp.MountPoints { 152 // Check if we have a volume for this mountpoint 153 tuple, exists := namedVolumeMounts[mp.Name] 154 if !exists || tuple.V.Name == "" { 155 return nil, fmt.Errorf("missing volume for mountpoint %q", mp.Name) 156 } 157 158 // If empty, fill in ReadOnly bit 159 if tuple.V.ReadOnly == nil { 160 v := tuple.V 161 v.ReadOnly = &mp.ReadOnly 162 namedVolumeMounts[mp.Name] = volumeMountTuple{M: tuple.M, V: v} 163 diag.Printf("adding %+v", namedVolumeMounts[mp.Name]) 164 } 165 } 166 167 // Gather host mounts which we make MS_SHARED if passed as a volume source 168 hostMounts, err := getHostMounts() 169 if err != nil { 170 return nil, errwrap.Wrap(errors.New("can't gather host mounts"), err) 171 } 172 173 argFlyMounts := []flyMount{} 174 var flags uintptr = syscall.MS_BIND // TODO: allow optional | syscall.MS_REC 175 for _, tuple := range namedVolumeMounts { 176 if _, isHostMount := hostMounts[tuple.V.Source]; isHostMount { 177 // Mark the host mount as SHARED so the container's changes to the mount are propagated to the host 178 argFlyMounts = append(argFlyMounts, 179 flyMount{"", "", tuple.V.Source, "none", syscall.MS_REC | syscall.MS_SHARED}, 180 ) 181 } 182 argFlyMounts = append(argFlyMounts, 183 flyMount{tuple.V.Source, rfs, tuple.M.Path, "none", flags}, 184 ) 185 186 if tuple.V.ReadOnly != nil && *tuple.V.ReadOnly { 187 argFlyMounts = append(argFlyMounts, 188 flyMount{"", rfs, tuple.M.Path, "none", flags | syscall.MS_REMOUNT | syscall.MS_RDONLY}, 189 ) 190 } 191 } 192 return argFlyMounts, nil 193 } 194 195 func stage1() int { 196 uuid, err := types.NewUUID(flag.Arg(0)) 197 if err != nil { 198 log.Print("UUID is missing or malformed\n") 199 return 1 200 } 201 202 root := "." 203 p, err := stage1commontypes.LoadPod(root, uuid) 204 if err != nil { 205 log.PrintE("can't load pod", err) 206 return 1 207 } 208 209 if len(p.Manifest.Apps) != 1 { 210 log.Printf("flavor %q only supports 1 application per Pod for now", flavor) 211 return 1 212 } 213 214 lfd, err := common.GetRktLockFD() 215 if err != nil { 216 log.PrintE("can't get rkt lock fd", err) 217 return 1 218 } 219 220 // set close-on-exec flag on RKT_LOCK_FD so it gets correctly closed after execution is finished 221 if err := sys.CloseOnExec(lfd, true); err != nil { 222 log.PrintE("can't set FD_CLOEXEC on rkt lock", err) 223 return 1 224 } 225 226 env := []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} 227 for _, e := range p.Manifest.Apps[0].App.Environment { 228 env = append(env, e.Name+"="+e.Value) 229 } 230 231 args := p.Manifest.Apps[0].App.Exec 232 rfs := filepath.Join(common.AppPath(p.Root, p.Manifest.Apps[0].Name), "rootfs") 233 234 argFlyMounts, err := evaluateMounts(rfs, string(p.Manifest.Apps[0].Name), p) 235 if err != nil { 236 log.PrintE("can't evaluate mounts", err) 237 return 1 238 } 239 240 effectiveMounts := append( 241 []flyMount{ 242 {"", "", "/dev", "none", syscall.MS_REC | syscall.MS_SHARED}, 243 {"/dev", rfs, "/dev", "none", syscall.MS_BIND | syscall.MS_REC}, 244 245 {"", "", "/proc", "none", syscall.MS_REC | syscall.MS_SHARED}, 246 {"/proc", rfs, "/proc", "none", syscall.MS_BIND | syscall.MS_REC}, 247 248 {"", "", "/sys", "none", syscall.MS_REC | syscall.MS_SHARED}, 249 {"/sys", rfs, "/sys", "none", syscall.MS_BIND | syscall.MS_REC}, 250 251 {"tmpfs", rfs, "/tmp", "tmpfs", 0}, 252 }, 253 argFlyMounts..., 254 ) 255 256 for _, mount := range effectiveMounts { 257 var ( 258 err error 259 hostPathInfo os.FileInfo 260 targetPathInfo os.FileInfo 261 ) 262 263 if strings.HasPrefix(mount.HostPath, "/") { 264 if hostPathInfo, err = os.Stat(mount.HostPath); err != nil { 265 log.PrintE(fmt.Sprintf("stat of host directory %s", mount.HostPath), err) 266 return 1 267 } 268 } else { 269 hostPathInfo = nil 270 } 271 272 absTargetPath := filepath.Join(mount.TargetPrefixPath, mount.RelTargetPath) 273 if targetPathInfo, err = os.Stat(absTargetPath); err != nil && !os.IsNotExist(err) { 274 log.PrintE(fmt.Sprintf("stat of target directory %s", absTargetPath), err) 275 return 1 276 } 277 278 switch { 279 case targetPathInfo == nil: 280 absTargetPathParent, _ := filepath.Split(absTargetPath) 281 if err := os.MkdirAll(absTargetPathParent, 0700); err != nil { 282 log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) 283 return 1 284 } 285 switch { 286 case hostPathInfo == nil || hostPathInfo.IsDir(): 287 if err := os.Mkdir(absTargetPath, 0700); err != nil { 288 log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) 289 return 1 290 } 291 case !hostPathInfo.IsDir(): 292 file, err := os.OpenFile(absTargetPath, os.O_CREATE, 0700) 293 if err != nil { 294 log.PrintE(fmt.Sprintf("can't create file %q", absTargetPath), err) 295 return 1 296 } 297 file.Close() 298 } 299 case hostPathInfo != nil: 300 switch { 301 case hostPathInfo.IsDir() && !targetPathInfo.IsDir(): 302 log.Printf("can't mount because %q is a directory while %q is not", mount.HostPath, absTargetPath) 303 return 1 304 case !hostPathInfo.IsDir() && targetPathInfo.IsDir(): 305 log.Printf("can't mount because %q is not a directory while %q is", mount.HostPath, absTargetPath) 306 return 1 307 } 308 } 309 310 if err := syscall.Mount(mount.HostPath, absTargetPath, mount.Fs, mount.Flags, ""); err != nil { 311 log.PrintE(fmt.Sprintf("can't mount %q on %q with flags %v", mount.HostPath, absTargetPath, mount.Flags), err) 312 return 1 313 } 314 } 315 316 if err = stage1common.WritePpid(os.Getpid()); err != nil { 317 log.Error(err) 318 return 4 319 } 320 321 diag.Printf("chroot to %q", rfs) 322 if err := syscall.Chroot(rfs); err != nil { 323 log.PrintE("can't chroot", err) 324 return 1 325 } 326 327 if err := os.Chdir("/"); err != nil { 328 log.PrintE("can't change to root new directory", err) 329 return 1 330 } 331 332 diag.Printf("execing %q in %q", args, rfs) 333 err = stage1common.WithClearedCloExec(lfd, func() error { 334 return syscall.Exec(args[0], args, env) 335 }) 336 if err != nil { 337 log.PrintE(fmt.Sprintf("can't execute %q", args[0]), err) 338 return 7 339 } 340 341 return 0 342 } 343 344 func main() { 345 flag.Parse() 346 347 log, diag, _ = rktlog.NewLogSet("run", debug) 348 if !debug { 349 log.SetOutput(ioutil.Discard) 350 } 351 352 // move code into stage1() helper so defered fns get run 353 os.Exit(stage1()) 354 }