func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error { if d.MockMode { return nil } entries, err := ioutil.ReadDir(shared.VarPath("containers")) if err != nil { return err } for _, entry := range entries { if !shared.IsDir(shared.VarPath("containers", entry.Name(), "rootfs")) { continue } info, err := os.Stat(shared.VarPath("containers", entry.Name(), "rootfs")) if err != nil { return err } if int(info.Sys().(*syscall.Stat_t).Uid) == 0 { err := os.Chmod(shared.VarPath("containers", entry.Name()), 0700) if err != nil { return err } err = os.Chown(shared.VarPath("containers", entry.Name()), 0, 0) if err != nil { return err } } } return nil }
func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error { if d.MockMode { return nil } entries, err := ioutil.ReadDir(shared.VarPath("containers")) if err != nil { /* If the directory didn't exist before, the user had never * started containers, so we don't need to fix up permissions * on anything. */ if os.IsNotExist(err) { return nil } return err } for _, entry := range entries { if !shared.IsDir(shared.VarPath("containers", entry.Name(), "rootfs")) { continue } info, err := os.Stat(shared.VarPath("containers", entry.Name(), "rootfs")) if err != nil { return err } if int(info.Sys().(*syscall.Stat_t).Uid) == 0 { err := os.Chmod(shared.VarPath("containers", entry.Name()), 0700) if err != nil { return err } err = os.Chown(shared.VarPath("containers", entry.Name()), 0, 0) if err != nil { return err } } } return nil }
func (d *Daemon) Init() error { d.shutdownChan = make(chan bool) /* Set the executable path */ absPath, err := os.Readlink("/proc/self/exe") if err != nil { return err } d.execPath = absPath /* Set the LVM environment */ err = os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1") if err != nil { return err } /* Setup logging if that wasn't done before */ if shared.Log == nil { shared.Log, err = logging.GetLogger("", "", true, true, nil) if err != nil { return err } } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect AppArmor support */ if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because of lack of kernel support") } _, err = exec.LookPath("apparmor_parser") if aaAvailable && err != nil { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.") } /* Detect AppArmor confinment */ if !aaConfined { profile := aaProfile() if profile != "unconfined" && profile != "" { aaConfined = true shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } /* Detect CGroup support */ cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") if !cgSwapAccounting { shared.Log.Warn("CGroup memory swap accounting is disabled, swap limits will be ignored.") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Set container path */ d.lxcpath = shared.VarPath("containers") /* Make sure all our directories are available */ if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devices"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("security"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("shmounts"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { d.pruneExpiredImages() for { timer := time.NewTimer(24 * time.Hour) timeChan := timer.C select { case <-timeChan: /* run once per day */ d.pruneExpiredImages() case <-d.pruneChan: /* run when image.remote_cache_expiry is changed */ d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ go func() { containersRestart(d) }() /* Start the scheduler */ go deviceTaskScheduler(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } for _, c := range apiInternal { d.createCmd("internal", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { c, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { return err } err = c.Finger() if err != nil { shared.Log.Debug("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } else { return fmt.Errorf("LXD is already running.") } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } var gid int if d.group != "" { gid, err = shared.GroupId(d.group) if err != nil { return err } } else { gid = os.Getgid() } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func (c *Client) ExportImage(image string, target string) (*Response, string, error) { uri := c.url(shared.APIVersion, "images", image, "export") raw, err := c.getRaw(uri) if err != nil { return nil, "", err } ctype, ctypeParams, err := mime.ParseMediaType(raw.Header.Get("Content-Type")) if err != nil { ctype = "application/octet-stream" } // Deal with split images if ctype == "multipart/form-data" { if !shared.IsDir(target) { return nil, "", fmt.Errorf(i18n.G("Split images can only be written to a directory.")) } // Parse the POST data mr := multipart.NewReader(raw.Body, ctypeParams["boundary"]) // Get the metadata tarball part, err := mr.NextPart() if err != nil { return nil, "", err } if part.FormName() != "metadata" { return nil, "", fmt.Errorf("Invalid multipart image") } imageTarf, err := os.OpenFile(part.FileName(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return nil, "", err } _, err = io.Copy(imageTarf, part) imageTarf.Close() if err != nil { return nil, "", err } // Get the rootfs tarball part, err = mr.NextPart() if err != nil { return nil, "", err } if part.FormName() != "rootfs" { return nil, "", fmt.Errorf("Invalid multipart image") } rootfsTarf, err := os.OpenFile(part.FileName(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return nil, "", err } _, err = io.Copy(rootfsTarf, part) rootfsTarf.Close() if err != nil { return nil, "", err } return nil, target, nil } // Deal with unified images var wr io.Writer var destpath string if target == "-" { wr = os.Stdout destpath = "stdout" } else if fi, err := os.Stat(target); err == nil { // file exists, so check if folder switch mode := fi.Mode(); { case mode.IsDir(): // save in directory, header content-disposition can not be null // and will have a filename cd := strings.Split(raw.Header["Content-Disposition"][0], "=") // write filename from header destpath = filepath.Join(target, cd[1]) f, err := os.Create(destpath) defer f.Close() if err != nil { return nil, "", err } wr = f default: // overwrite file destpath = target f, err := os.OpenFile(destpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) defer f.Close() if err != nil { return nil, "", err } wr = f } } else { // write as simple file destpath = target f, err := os.Create(destpath) defer f.Close() wr = f if err != nil { return nil, "", err } } _, err = io.Copy(wr, raw.Body) if err != nil { return nil, "", err } // it streams to stdout or file, so no response returned return nil, destpath, nil }
func run() error { gnuflag.Usage = func() { fmt.Printf("Usage: lxd [command] [options]\n\nOptions:\n") gnuflag.PrintDefaults() fmt.Printf("\nCommands:\n") fmt.Printf(" shutdown\n") fmt.Printf(" Perform a clean shutdown of LXD and all running containers\n") fmt.Printf(" activateifneeded\n") fmt.Printf(" Check if LXD should be started (at boot) and if so, spawn it through socket activation\n") fmt.Printf("\nInternal commands (don't call directly):\n") fmt.Printf(" forkgetfile\n") fmt.Printf(" Grab a file from a running container\n") fmt.Printf(" forkputfile\n") fmt.Printf(" Pushes a file to a running container\n") fmt.Printf(" forkstart\n") fmt.Printf(" Start a container\n") fmt.Printf(" forkmigrate\n") fmt.Printf(" Restore a container after migration\n") } gnuflag.Parse(true) if *help { // The user asked for help via --help, so we shouldn't print to // stderr. gnuflag.SetOut(os.Stdout) gnuflag.Usage() return nil } if *version { fmt.Println(shared.Version) return nil } // Configure logging syslog := "" if *syslogFlag { syslog = "lxd" } err := shared.SetLogger(syslog, *logfile, *verbose, *debug) if err != nil { fmt.Printf("%s", err) return nil } // Process sub-commands if len(os.Args) > 1 { // "forkputfile" and "forkgetfile" are handled specially in copyfile.go switch os.Args[1] { case "forkstart": return startContainer(os.Args[1:]) case "forkmigrate": return migration.MigrateContainer(os.Args[1:]) case "shutdown": return cleanShutdown() case "activateifneeded": return activateIfNeeded() } } if gnuflag.NArg() != 0 { gnuflag.Usage() return fmt.Errorf("Unknown arguments") } if *cpuProfile != "" { f, err := os.Create(*cpuProfile) if err != nil { fmt.Printf("Error opening cpu profile file: %s\n", err) return nil } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } if *memProfile != "" { go memProfiler() } neededPrograms := []string{"setfacl", "rsync", "tar", "xz"} for _, p := range neededPrograms { _, err := exec.LookPath(p) if err != nil { return err } } _, err = exec.LookPath("apparmor_parser") if err == nil && shared.IsDir("/sys/kernel/security/apparmor") { aaEnabled = true } else { shared.Log.Warn("apparmor_parser binary not found or apparmor " + "fs not mounted. AppArmor disabled.") } /* Can we create devices? */ checkCanMknod() if *printGoroutines > 0 { go func() { for { time.Sleep(time.Duration(*printGoroutines) * time.Second) shared.PrintStack() } }() } d, err := startDaemon() if err != nil { if d != nil && d.db != nil { d.db.Close() } return err } var ret error var wg sync.WaitGroup wg.Add(1) go func() { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGPWR) sig := <-ch shared.Log.Info( fmt.Sprintf("Received '%s signal', shutting down containers.", sig)) containersShutdown(d) ret = d.Stop() wg.Done() }() go func() { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGINT) signal.Notify(ch, syscall.SIGQUIT) signal.Notify(ch, syscall.SIGTERM) sig := <-ch shared.Log.Info(fmt.Sprintf("Received '%s signal', exiting.\n", sig)) ret = d.Stop() wg.Done() }() wg.Wait() return ret }
func (d *Daemon) Init() error { /* Initialize some variables */ d.imagesDownloading = map[string]chan bool{} d.readyChan = make(chan bool) d.shutdownChan = make(chan bool) /* Set the executable path */ absPath, err := os.Readlink("/proc/self/exe") if err != nil { return err } d.execPath = absPath /* Set the LVM environment */ err = os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1") if err != nil { return err } /* Setup logging if that wasn't done before */ if shared.Log == nil { shared.Log, err = logging.GetLogger("", "", true, true, nil) if err != nil { return err } } /* Print welcome message */ if d.MockMode { shared.Log.Info("LXD is starting in mock mode", log.Ctx{"path": shared.VarPath("")}) } else if d.SetupMode { shared.Log.Info("LXD is starting in setup mode", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("LXD is starting in normal mode", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect AppArmor support */ if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because of lack of kernel support") } _, err = exec.LookPath("apparmor_parser") if aaAvailable && err != nil { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.") } /* Detect AppArmor confinment */ if !aaConfined { profile := aaProfile() if profile != "unconfined" && profile != "" { aaConfined = true shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } /* Detect CGroup support */ cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/") if !cgBlkioController { shared.Log.Warn("Couldn't find the CGroup blkio controller, I/O limits will be ignored.") } cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgDevicesController = shared.PathExists("/sys/fs/cgroup/devices/") if !cgDevicesController { shared.Log.Warn("Couldn't find the CGroup devices controller, device access control won't work.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgNetPrioController = shared.PathExists("/sys/fs/cgroup/net_prio/") if !cgNetPrioController { shared.Log.Warn("Couldn't find the CGroup network class controller, network limits will be ignored.") } cgPidsController = shared.PathExists("/sys/fs/cgroup/pids/") if !cgPidsController { shared.Log.Warn("Couldn't find the CGroup pids controller, process limits will be ignored.") } cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") if !cgSwapAccounting { shared.Log.Warn("CGroup memory swap accounting is disabled, swap limits will be ignored.") } /* Get the list of supported architectures */ var architectures = []int{} architectureName, err := shared.ArchitectureGetLocal() if err != nil { return err } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Set container path */ d.lxcpath = shared.VarPath("containers") /* Make sure all our directories are available */ if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devices"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.LogPath(), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("security"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("shmounts"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Setup the storage driver */ if !d.MockMode { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } } /* Load all config values from the database */ _, err = d.ConfigValuesGet() if err != nil { return err } /* set the initial proxy function based on config values in the DB */ d.updateProxy() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } if !d.MockMode { /* Start the scheduler */ go deviceEventListener(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } cert, err := tls.LoadX509KeyPair(certf, keyf) if err != nil { return err } tlsConfig := &tls.Config{ InsecureSkipVerify: true, ClientAuth: tls.RequestClientCert, Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, PreferServerCipherSuites: true, } tlsConfig.BuildNameToCertificate() d.tlsConfig = tlsConfig readSavedClientCAList(d) } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.StrictSlash(false) d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } for _, c := range apiInternal { d.createCmd("internal", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { d.UnixSocket = &Socket{Socket: listener, CloseOnExit: false} } else { tlsListener := tls.NewListener(listener, d.tlsConfig) d.TCPSocket = &Socket{Socket: tlsListener, CloseOnExit: false} } } } else { shared.Log.Info("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { _, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { shared.Log.Debug("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } else { return fmt.Errorf("LXD is already running.") } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } var gid int if d.group != "" { gid, err = shared.GroupId(d.group) if err != nil { return err } } else { gid = os.Getgid() } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } d.UnixSocket = &Socket{Socket: unixl, CloseOnExit: true} } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, d.tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { if d.TCPSocket != nil { shared.Log.Info("Replacing systemd TCP socket by configure one") d.TCPSocket.Socket.Close() } d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true} } } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") if d.UnixSocket != nil { shared.Log.Info(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()}) d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) }) } if d.TCPSocket != nil { shared.Log.Info(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()}) d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) if !d.MockMode && !d.SetupMode { err := d.Ready() if err != nil { return err } } return nil }
func deviceToLxc(d shared.Device) ([][]string, error) { switch d["type"] { case "unix-char": return nil, fmt.Errorf("Not implemented") case "unix-block": return nil, fmt.Errorf("Not implemented") case "nic": if d["nictype"] != "bridged" && d["nictype"] != "" { return nil, fmt.Errorf("Bad nic type: %s\n", d["nictype"]) } var l1 = []string{"lxc.network.type", "veth"} var lines = [][]string{l1} var l2 []string if d["hwaddr"] != "" { l2 = []string{"lxc.network.hwaddr", d["hwaddr"]} lines = append(lines, l2) } if d["mtu"] != "" { l2 = []string{"lxc.network.mtu", d["mtu"]} lines = append(lines, l2) } if d["parent"] != "" { l2 = []string{"lxc.network.link", d["parent"]} lines = append(lines, l2) } if d["name"] != "" { l2 = []string{"lxc.network.name", d["name"]} lines = append(lines, l2) } return lines, nil case "disk": var p string configLines := [][]string{} if d["path"] == "/" || d["path"] == "" { p = "" } else if d["path"][0:1] == "/" { p = d["path"][1:] } else { p = d["path"] } /* TODO - check whether source is a disk, loopback, btrfs subvol, etc */ /* for now we only handle directory bind mounts */ source := d["source"] fstype := "none" options := []string{} var err error if shared.IsBlockdevPath(d["source"]) { fstype, err = shared.BlockFsDetect(d["source"]) if err != nil { return nil, fmt.Errorf("Error setting up %s: %s\n", d["name"], err) } l, err := addBlockDev(d["source"]) if err != nil { return nil, fmt.Errorf("Error adding blockdev: %s\n", err) } configLines = append(configLines, l) } else if shared.IsDir(source) { options = append(options, "bind") options = append(options, "create=dir") } else /* file bind mount */ { /* Todo - can we distinguish between file bind mount and * a qcow2 (or other fs container) file? */ options = append(options, "bind") options = append(options, "create=file") } if d["readonly"] == "1" || d["readonly"] == "true" { options = append(options, "ro") } if d["optional"] == "1" || d["optional"] == "true" { options = append(options, "optional") } opts := strings.Join(options, ",") if opts == "" { opts = "defaults" } l := []string{"lxc.mount.entry", fmt.Sprintf("%s %s %s %s 0 0", source, p, fstype, opts)} configLines = append(configLines, l) return configLines, nil case "none": return nil, nil default: return nil, fmt.Errorf("Bad device type") } }
func (d *Daemon) Init() error { /* Setup logging */ if shared.Log == nil { shared.SetLogger("", "", true, true) } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect apparmor support */ if aaEnabled && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles have been manually disabled") } if aaEnabled && !shared.IsDir("/sys/kernel/security/apparmor") { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because of lack of kernel support") } if aaEnabled && !haveMacAdmin() { shared.Log.Warn("Per-container AppArmor profiles are disabled because mac_admin capability is missing.") aaEnabled = false } _, err := exec.LookPath("apparmor_parser") if aaEnabled && err != nil { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because 'apparmor_parser' couldn't be found") } if aaEnabled && runningInUserns { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because LXD is running inside a user namespace") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Create required paths */ d.lxcpath = shared.VarPath("containers") err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return err } // Create default directories if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { for { expiryStr, err := dbImageExpiryGet(d.db) var expiry int if err != nil { expiry = 10 } else { expiry, err = strconv.Atoi(expiryStr) if err != nil { expiry = 10 } if expiry <= 0 { expiry = 1 } } timer := time.NewTimer(time.Duration(expiry) * 24 * time.Hour) timeChan := timer.C select { case <-timeChan: d.pruneExpiredImages() case <-d.pruneChan: d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ containersRestart(d) containersWatch(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Log.Debug("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } gid, err := shared.GroupId(*group) if err != nil { return err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func containerValidDevices(devices shared.Devices, profile bool, expanded bool) error { // Empty device list if devices == nil { return nil } // Check each device individually for name, m := range devices { if m["type"] == "" { return fmt.Errorf("Missing device type for device '%s'", name) } if !shared.StringInSlice(m["type"], []string{"none", "nic", "disk", "unix-char", "unix-block", "usb", "gpu"}) { return fmt.Errorf("Invalid device type for device '%s'", name) } for k, _ := range m { if !containerValidDeviceConfigKey(m["type"], k) { return fmt.Errorf("Invalid device configuration key for %s: %s", m["type"], k) } } if m["type"] == "nic" { if m["nictype"] == "" { return fmt.Errorf("Missing nic type") } if !shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "p2p", "macvlan"}) { return fmt.Errorf("Bad nic type: %s", m["nictype"]) } if shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "macvlan"}) && m["parent"] == "" { return fmt.Errorf("Missing parent for %s type nic.", m["nictype"]) } } else if m["type"] == "disk" { if m["path"] == "" { return fmt.Errorf("Disk entry is missing the required \"path\" property.") } if m["source"] == "" && m["path"] != "/" { return fmt.Errorf("Disk entry is missing the required \"source\" property.") } if m["path"] == "/" && m["source"] != "" { return fmt.Errorf("Root disk entry may not have a \"source\" property set.") } if m["size"] != "" && m["path"] != "/" { return fmt.Errorf("Only the root disk may have a size quota.") } if (m["path"] == "/" || !shared.IsDir(m["source"])) && m["recursive"] != "" { return fmt.Errorf("The recursive option is only supported for additional bind-mounted paths.") } } else if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) { if m["path"] == "" { return fmt.Errorf("Unix device entry is missing the required \"path\" property.") } } else if m["type"] == "usb" { if m["vendorid"] == "" { return fmt.Errorf("Missing vendorid for USB device.") } } else if m["type"] == "gpu" { // Probably no checks needed, since we allow users to // pass in all GPUs. } else if m["type"] == "none" { continue } else { return fmt.Errorf("Invalid device type: %s", m["type"]) } } // Checks on the expanded config if expanded { foundRootfs := false for _, m := range devices { if m["type"] == "disk" && m["path"] == "/" { foundRootfs = true } } if !foundRootfs { return fmt.Errorf("Container is lacking rootfs entry") } } return nil }
func containerSnapRestore(d *Daemon, name string, snap string) error { // normalize snapshot name if !shared.IsSnapshot(snap) { snap = fmt.Sprintf("%s/%s", name, snap) } shared.Debugf("RESTORE => Restoring snapshot [%s] on container [%s]", snap, name) /* * restore steps: * 1. stop container if already running * 2. overwrite existing config with snapshot config * 3. copy snapshot rootfs to container */ wasRunning := false c, err := newLxdContainer(name, d) if err != nil { shared.Debugf("RESTORE => Error: newLxdContainer() failed for container", err) return err } // 1. stop container // TODO: stateful restore ? if c.c.Running() { wasRunning = true if err = c.Stop(); err != nil { shared.Debugf("RESTORE => Error: could not stop container", err) return err } shared.Debugf("RESTORE => Stopped container %s", name) } // 2, replace config // Make sure the source exists. source, err := newLxdContainer(snap, d) if err != nil { shared.Debugf("RESTORE => Error: newLxdContainer() failed for snapshot", err) return err } newConfig := containerConfigReq{} newConfig.Config = source.config newConfig.Profiles = source.profiles newConfig.Devices = source.devices err = containerReplaceConfig(d, c, name, newConfig) if err != nil { shared.Debugf("RESTORE => err #4", err) return err } // 3. copy rootfs // TODO: btrfs optimizations containerRootPath := shared.VarPath("lxc", name) if !shared.IsDir(path.Dir(containerRootPath)) { shared.Debugf("RESTORE => containerRoot [%s] directory does not exist", containerRootPath) return os.ErrNotExist } var snapshotRootFSPath string snapshotRootFSPath = migration.AddSlash(snapshotRootfsDir(c, strings.SplitN(snap, "/", 2)[1])) containerRootFSPath := migration.AddSlash(fmt.Sprintf("%s/%s", containerRootPath, "rootfs")) shared.Debugf("RESTORE => Copying %s to %s", snapshotRootFSPath, containerRootFSPath) rsyncVerbosity := "-q" if *debug { rsyncVerbosity = "-vi" } output, err := exec.Command("rsync", "-a", "-c", "-HAX", "--devices", "--delete", rsyncVerbosity, snapshotRootFSPath, containerRootFSPath).CombinedOutput() shared.Debugf("RESTORE => rsync output\n%s", output) if err == nil && !source.isPrivileged() { err = setUnprivUserAcl(c, containerRootPath) if err != nil { shared.Debugf("Error adding acl for container root: falling back to chmod\n") output, err := exec.Command("chmod", "+x", containerRootPath).CombinedOutput() if err != nil { shared.Debugf("Error chmoding the container root\n") shared.Debugf(string(output)) return err } } } else { shared.Debugf("rsync failed:\n%s", output) return err } if wasRunning { c.Start() } return nil }
func DeviceToLxc(d shared.Device) ([][]string, error) { switch d["type"] { case "unix-char": return nil, fmt.Errorf("Not implemented") case "unix-block": return nil, fmt.Errorf("Not implemented") case "nic": if d["nictype"] != "bridged" && d["nictype"] != "" { return nil, fmt.Errorf("Bad nic type: %s\n", d["nictype"]) } var l1 = []string{"lxc.network.type", "veth"} var lines = [][]string{l1} var l2 []string if d["hwaddr"] != "" { l2 = []string{"lxc.network.hwaddr", d["hwaddr"]} lines = append(lines, l2) } if d["mtu"] != "" { l2 = []string{"lxc.network.mtu", d["mtu"]} lines = append(lines, l2) } if d["parent"] != "" { l2 = []string{"lxc.network.link", d["parent"]} lines = append(lines, l2) } if d["name"] != "" { l2 = []string{"lxc.network.name", d["name"]} lines = append(lines, l2) } return lines, nil case "disk": var p string if d["path"] == "/" || d["path"] == "" { p = "" } else if d["path"][0:1] == "/" { p = d["path"][1:] } else { p = d["path"] } /* TODO - check whether source is a disk, loopback, btrfs subvol, etc */ /* for now we only handle directory bind mounts */ source := d["source"] opts := "bind" if shared.IsDir(source) { opts = fmt.Sprintf("%s,create=dir", opts) } else { opts = fmt.Sprintf("%s,create=file", opts) } if d["readonly"] == "1" || d["readonly"] == "true" { opts = fmt.Sprintf("%s,ro", opts) } if d["optional"] == "1" || d["optional"] == "true" { opts = fmt.Sprintf("%s,optional", opts) } l := []string{"lxc.mount.entry", fmt.Sprintf("%s %s none %s 0 0", source, p, opts)} return [][]string{l}, nil case "none": return nil, nil default: return nil, fmt.Errorf("Bad device type") } }
func deviceToLxc(cntPath string, d shared.Device) ([][]string, error) { switch d["type"] { case "unix-char": return unixDevCgroup(d) case "unix-block": return unixDevCgroup(d) case "nic": // A few checks if d["nictype"] == "" { return nil, fmt.Errorf("Missing nic type") } if !shared.StringInSlice(d["nictype"], []string{"bridged", "physical", "p2p", "macvlan"}) { return nil, fmt.Errorf("Bad nic type: %s", d["nictype"]) } if shared.StringInSlice(d["nictype"], []string{"bridged", "physical", "macvlan"}) && d["parent"] == "" { return nil, fmt.Errorf("Missing parent for %s type nic.", d["nictype"]) } // Generate the LXC config var line []string var lines = [][]string{} if shared.StringInSlice(d["nictype"], []string{"bridged", "p2p"}) { line = []string{"lxc.network.type", "veth"} lines = append(lines, line) } else if d["nictype"] == "physical" { line = []string{"lxc.network.type", "phys"} lines = append(lines, line) } else if d["nictype"] == "macvlan" { line = []string{"lxc.network.type", "macvlan"} lines = append(lines, line) line = []string{"lxc.network.macvlan.mode", "bridge"} lines = append(lines, line) } if d["hwaddr"] != "" { line = []string{"lxc.network.hwaddr", d["hwaddr"]} lines = append(lines, line) } if d["mtu"] != "" { line = []string{"lxc.network.mtu", d["mtu"]} lines = append(lines, line) } if shared.StringInSlice(d["nictype"], []string{"bridged", "physical", "macvlan"}) { line = []string{"lxc.network.link", d["parent"]} lines = append(lines, line) } if d["name"] != "" { line = []string{"lxc.network.name", d["name"]} lines = append(lines, line) } return lines, nil case "disk": var p string configLines := [][]string{} if d["path"] == "/" || d["path"] == "" { p = "" } else if d["path"][0:1] == "/" { p = d["path"][1:] } else { p = d["path"] } source := d["source"] options := []string{} if shared.IsBlockdevPath(d["source"]) { l, err := mountTmpBlockdev(cntPath, d) if err != nil { return nil, fmt.Errorf("Error adding blockdev: %s", err) } configLines = append(configLines, l) return configLines, nil } else if shared.IsDir(source) { options = append(options, "bind") options = append(options, "create=dir") } else /* file bind mount */ { /* Todo - can we distinguish between file bind mount and * a qcow2 (or other fs container) file? */ options = append(options, "bind") options = append(options, "create=file") } if d["readonly"] == "1" || d["readonly"] == "true" { options = append(options, "ro") } if d["optional"] == "1" || d["optional"] == "true" { options = append(options, "optional") } opts := strings.Join(options, ",") if opts == "" { opts = "defaults" } l := []string{"lxc.mount.entry", fmt.Sprintf("%s %s %s %s 0 0", source, p, "none", opts)} configLines = append(configLines, l) return configLines, nil case "none": return nil, nil default: return nil, fmt.Errorf("Bad device type") } }