// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. // isulad-tools is licensed under the Mulan PSL v1. // You can use this software according to the terms and conditions of the Mulan PSL v1. // You may obtain a copy of Mulan PSL v1 at: // http://license.coscl.org.cn/MulanPSL // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR // PURPOSE. // See the Mulan PSL v1 for more details. // Description: isulad hook main function // Author: zhangwei // Create: 2018-01-18 // go base main package package main import ( "flag" "fmt" "os" "os/signal" "path/filepath" "syscall" "github.com/docker/docker/pkg/reexec" "github.com/opencontainers/runc/libcontainer/configs" _ "github.com/opencontainers/runc/libcontainer/nsenter" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" hconfig "isula.org/isulad-tools/config" "isula.org/isulad-tools/container" "isula.org/isulad-tools/utils" ) var ( defaultHookConfigFile = "device_hook.json" syslogTag = "hook " bundleConfigFile = "ociconfig.json" ) type hookData struct { spec *specs.Spec state *configs.HookState hookConfig *hconfig.ContainerHookConfig storagePath string } func setupLog(logfile string) { logrus.SetLevel(logrus.DebugLevel) logrus.SetOutput(os.Stdout) if logfile != "" { f, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0600) if err != nil { return } logrus.SetOutput(f) return } fm := &logrus.TextFormatter{DisableTimestamp: true, DisableColors: true} logrus.SetFormatter(fm) if err := utils.HookSyslog("", syslogTag); err != nil { fmt.Fprintf(os.Stdout, "%v", err) } } func fatal(err error) { if err != nil { logrus.Error(err) fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func prepareHookData() (*hookData, error) { var ( err error spec *specs.Spec compatSpec *specs.CompatSpec state *configs.HookState hookConfig *hconfig.ContainerHookConfig containerStoragePath = "" ) if state, err = utils.ParseHookState(os.Stdin); err != nil { logrus.Errorf("Parse Hook State Failed: %v", err) return nil, err } // Load container OCI spec from config configFile := bundleConfigFile if spec, err = utils.LoadSpec(filepath.Join(state.Bundle, configFile)); err != nil { if compatSpec, err = utils.LoadCompatSpec(filepath.Join(state.Bundle, configFile)); err != nil { logrus.Errorf("Failed to load spec for contianer %s: %v", state.ID, err) return nil, err } } if compatSpec != nil { capabilities := compatSpec.Process.Capabilities spec = &compatSpec.Spec spec.Process = compatSpec.Process.Process spec.Process.Capabilities = &specs.LinuxCapabilities{ Bounding: capabilities, Effective: capabilities, Inheritable: capabilities, Permitted: capabilities, Ambient: capabilities, } } if containerStoragePath, err = utils.GetContainerStoragePath(); err != nil { logrus.Errorf("Failed to get container storage path: %v", err) return nil, err } configPath := filepath.Join(containerStoragePath, state.ID, defaultHookConfigFile) if _, err := os.Stat(configPath); err != nil { // not an error, user do not add/remove device to/from this container. return &hookData{ spec: spec, state: state, hookConfig: &hconfig.ContainerHookConfig{}, storagePath: containerStoragePath, }, nil } // Load devices and binds config for container. if hookConfig, err = hconfig.LoadContainerHookConfig(configPath); err != nil { logrus.Errorf("Failed to parse Config File for container %s: %v", state.ID, err) return nil, err } return &hookData{ spec: spec, state: state, hookConfig: hookConfig, storagePath: containerStoragePath, }, nil } func main() { if reexec.Init() { // `reexec routine` was registered in isulad-tools/libdevice // Sub nsenter process will come here. // Isulad reexec package do not handle errors. // And sub device-hook nsenter init process will send back the error message to parenet through pipe. // So here do not need to handle errors. return } signal.Ignore(syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM) flLogfile := flag.String("log", "", "set output log file") flMode := flag.String("state", "", "set isulad hook state mode: prestart or poststop") // No requirements at present, by default don't enable this function. flWithRelabel := flag.Bool("with-relabel", false, "isulad hook enable oci relabel hook function") flag.Parse() setupLog(*flLogfile) hData, err := prepareHookData() if err != nil { return } if err := os.MkdirAll(hconfig.IsuladToolsDir, 0666); err != nil { logrus.Errorf("failed to set isulad-tools dir: %v", err) } switch *flMode { case "prestart": if hData.state.Pid <= 0 { logrus.Errorf("can't get correct pid of container: %d", hData.state.Pid) return } if err := prestartHook(hData, *flWithRelabel); err != nil { fatal(err) } if err := updateHookData(hData); err != nil { fatal(err) } case "poststart": if hData.state.Pid <= 0 { logrus.Errorf("can't get correct pid of container: %d", hData.state.Pid) return } if err := poststartHook(hData, *flWithRelabel); err != nil { fatal(err) } if err := updateHookData(hData); err != nil { fatal(err) } case "poststop": postStopHook(hData, *flWithRelabel) } } func updateHookData(data *hookData) error { var ( err error containerStoragePath = "" ) c := &container.Container{} if data.storagePath != "" { containerStoragePath = data.storagePath } else { containerStoragePath, err = utils.GetContainerStoragePath() if err != nil { logrus.Errorf("Failed to get container storage path: %v", err) return err } } c.SetContainerPath(filepath.Join(containerStoragePath, data.state.ID)) if err := c.Lock(); err != nil { return err } defer c.Unlock() defer func() { if err := data.hookConfig.Flush(); err != nil { logrus.Infof("config Flush error:%v", err) } }() return nil }