func eventsSocket(r *http.Request, w http.ResponseWriter) error { listener := eventListener{} typeStr := r.FormValue("type") if typeStr == "" { typeStr = "logging,operation" } c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil) if err != nil { return err } listener.active = make(chan bool, 1) listener.connection = c listener.id = uuid.NewRandom().String() listener.messageTypes = strings.Split(typeStr, ",") eventsLock.Lock() eventListeners[listener.id] = &listener eventsLock.Unlock() shared.Debugf("New events listener: %s", listener.id) <-listener.active eventsLock.Lock() delete(eventListeners, listener.id) eventsLock.Unlock() listener.connection.Close() shared.Debugf("Disconnected events listener: %s", listener.id) return nil }
func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { unixConn := conn.(*net.UnixConn) switch state { case http.StateNew: pid, err := getPid(unixConn) if err != nil { shared.Debugf("Error getting pid for conn %s", err) } else { m.m[unixConn] = pid } case http.StateActive: return case http.StateIdle: return case http.StateHijacked: /* * The "Hijacked" state indicates that the connection has been * taken over from net/http. This is useful for things like * developing websocket libraries, who want to upgrade the * connection to a websocket one, and not use net/http any * more. Whatever the case, we want to forget about it since we * won't see it either. */ delete(m.m, unixConn) case http.StateClosed: delete(m.m, unixConn) default: shared.Debugf("Unknown state for connection %s", state) } }
func (d *Daemon) verifyAdminPwd(password string) bool { value, err := dbPasswordGet(d.db) if err != nil { shared.Debugf("verifyAdminPwd: %s", err) return false } buff, err := hex.DecodeString(value) if err != nil { shared.Debugf("hex decode failed") return false } salt := buff[0:PW_SALT_BYTES] hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, PW_HASH_BYTES) if err != nil { shared.Debugf("failed to create hash to check") return false } if !bytes.Equal(hash, buff[PW_SALT_BYTES:]) { shared.Debugf("Bad password received") return false } shared.Debugf("Verified the admin password") return true }
func untar(tarball string, path string) error { extractArgs, _, err := detectCompression(tarball) if err != nil { return err } command := "tar" args := []string{} if !canMknod { // if we are running in a userns where we cannot mknod, // then run with a seccomp filter which turns mknod into a // a noop. The container config had better know how to bind // mount the devices in at container start. args = append(args, "--exclude=dev/*") } args = append(args, "-C", path, "--numeric-owner") args = append(args, extractArgs...) args = append(args, tarball) output, err := exec.Command(command, args...).CombinedOutput() if err != nil { shared.Debugf("Unpacking failed") shared.Debugf(string(output)) return err } return nil }
func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) { if op.class != operationClassWebsocket { return nil, fmt.Errorf("Only websocket operations can be connected") } if op.status != shared.Running { return nil, fmt.Errorf("Only running operations can be connected") } chanConnect := make(chan error, 1) op.lock.Lock() go func(op *operation, chanConnect chan error) { err := op.onConnect(op, r, w) if err != nil { chanConnect <- err shared.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err) return } chanConnect <- nil shared.Debugf("Handled %s operation: %s", op.class.String(), op.id) }(op, chanConnect) op.lock.Unlock() shared.Debugf("Connected %s operation: %s", op.class.String(), op.id) return chanConnect, nil }
func main() { if err := run(); err != nil { // The action we take depends on the error we get. switch t := err.(type) { case *url.Error: shared.Debugf("url.Error caught in main(). Op: %s, URL: %s, Err: %s\n", t.Op, t.URL, t.Err) switch u := t.Err.(type) { case *net.OpError: shared.Debugf("Inner error type is a net.OpError: Op: %s Net: %s Addr: %s Err: %T", u.Op, u.Net, u.Addr, u.Err) if u.Op == "dial" && u.Net == "unix" { // The unix socket we are trying to conect to is refusing our connection attempt. Perhaps the server is not running? // Let's at least tell the user about it, since it's hard to get information on wether something is actually listening. fmt.Fprintf(os.Stderr, fmt.Sprintf(gettext.Gettext("Cannot connect to unix socket at %s Is the server running?\n"), u.Addr)) os.Exit(1) } default: shared.Debugf("url.Error's inner Err type is %T", u) } default: shared.Debugf("Error caught in main: %T\n", t) } fmt.Fprintf(os.Stderr, gettext.Gettext("error: %v\n"), err) os.Exit(1) } }
// RsyncSend sets up the sending half of an rsync, to recursively send the // directory pointed to by path over the websocket. func RsyncSend(path string, conn *websocket.Conn) error { cmd, dataSocket, stderr, err := rsyncSendSetup(path) if dataSocket != nil { defer dataSocket.Close() } if err != nil { return err } readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, dataSocket) output, err := ioutil.ReadAll(stderr) if err != nil { shared.Debugf("problem reading rsync stderr %s", err) } if err := cmd.Wait(); err != nil { shared.Debugf("problem with rsync send of %s: %s: %s", path, err, string(output)) } <-readDone <-writeDone return err }
func removeImgWorkdir(d *Daemon, builddir string) { vgname, _, err := getServerConfigValue(d, "core.lvm_vg_name") if err != nil { shared.Debugf("Error checking server config: %v", err) } matches, _ := filepath.Glob(fmt.Sprintf("%s/*.lv", builddir)) if len(matches) > 0 { if len(matches) > 1 { shared.Debugf("Unexpected - more than one .lv file in builddir. using first: %v", matches) } lvsymlink := matches[0] if lvpath, err := os.Readlink(lvsymlink); err != nil { shared.Debugf("Error reading target of symlink '%s'", lvsymlink) } else { err = shared.LVMRemoveLV(vgname, filepath.Base(lvpath)) if err != nil { shared.Debugf("Error removing LV '%s': %v", lvpath, err) } } } if d.BackingFs == "btrfs" { /* cannot rm -rf /a if /a/b is a subvolume, so first delete subvolumes */ /* todo: find the .btrfs file under dir */ fnamelist, _ := shared.ReadDir(builddir) for _, fname := range fnamelist { subvol := filepath.Join(builddir, fname) btrfsDeleteSubvol(subvol) } } if remErr := os.RemoveAll(builddir); remErr != nil { shared.Debugf("Error deleting temporary directory: %s", remErr) } }
func untar(tarball string, path string) error { extractArgs, _, err := detectCompression(tarball) if err != nil { return err } command := "tar" args := []string{} if runningInUserns { args = append(args, "--wildcards") args = append(args, "--exclude=dev/*") args = append(args, "--exclude=./dev/*") args = append(args, "--exclude=rootfs/dev/*") args = append(args, "--exclude=rootfs/./dev/*") } args = append(args, "-C", path, "--numeric-owner") args = append(args, extractArgs...) args = append(args, tarball) output, err := exec.Command(command, args...).CombinedOutput() if err != nil { shared.Debugf("Unpacking failed") shared.Debugf(string(output)) return err } return nil }
func rsyncWebsocket(cmd *exec.Cmd, conn *websocket.Conn) error { stdin, err := cmd.StdinPipe() if err != nil { return err } stdout, err := cmd.StdoutPipe() if err != nil { return err } stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } shared.WebsocketMirror(conn, stdin, stdout) data, err2 := ioutil.ReadAll(stderr) if err2 != nil { shared.Debugf("error reading rsync stderr: %s", err2) return err2 } shared.Debugf("Stderr from rsync: %s", data) err = cmd.Wait() if err != nil { shared.Debugf("rsync recv error %s: %s", err, string(data)) } return err }
func untarImage(imagefname string, destpath string) error { compression, _, err := detectCompression(imagefname) if err != nil { return err } args := []string{"-C", destpath, "--numeric-owner"} switch compression { case COMPRESSION_TAR: args = append(args, "-xf") case COMPRESSION_GZIP: args = append(args, "-zxf") case COMPRESSION_BZ2: args = append(args, "--jxf") case COMPRESSION_LZMA: args = append(args, "--lzma", "-xf") default: args = append(args, "-Jxf") } args = append(args, imagefname) output, err := exec.Command("tar", args...).CombinedOutput() if err != nil { shared.Debugf("image unpacking failed\n") shared.Debugf(string(output)) return err } return nil }
func deviceTaskScheduler(d *Daemon) { chHotplug, err := deviceMonitorProcessors() if err != nil { shared.Log.Error("scheduler: couldn't setup uevent watcher, no automatic re-balance") return } shared.Debugf("Scheduler: doing initial balance") deviceTaskBalance(d) for { select { case e := <-chHotplug: if len(e) != 2 { shared.Log.Error("Scheduler: received an invalid hotplug event") continue } shared.Debugf("Scheduler: %s is now %s: re-balancing", e[0], e[1]) deviceTaskBalance(d) case e := <-deviceSchedRebalance: if len(e) != 3 { shared.Log.Error("Scheduler: received an invalid rebalance event") continue } shared.Debugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2]) deviceTaskBalance(d) } } }
func (d *Daemon) verifyAdminPwd(password string) bool { q := "SELECT value FROM config WHERE key=\"core.trust_password\"" value := "" argIn := []interface{}{} argOut := []interface{}{&value} err := dbQueryRowScan(d.db, q, argIn, argOut) if err != nil || value == "" { shared.Debugf("verifyAdminPwd: no password is set") return false } buff, err := hex.DecodeString(value) if err != nil { shared.Debugf("hex decode failed") return false } salt := buff[0:PW_SALT_BYTES] hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, PW_HASH_BYTES) if err != nil { shared.Debugf("failed to create hash to check") return false } if !bytes.Equal(hash, buff[PW_SALT_BYTES:]) { shared.Debugf("Bad password received") return false } shared.Debugf("Verified the admin password") return true }
func deviceEventListener(d *Daemon) { chNetlink, err := deviceNetlinkListener() if err != nil { shared.Log.Error("scheduler: couldn't setup netlink listener") return } for { select { case e := <-chNetlink: if len(e) != 3 { shared.Log.Error("Scheduler: received an invalid hotplug event") continue } if e[0] == "cpu" { shared.Debugf("Scheduler: %s: %s is now %s: re-balancing", e[0], e[1], e[2]) deviceTaskBalance(d) } if e[0] == "net" && e[2] == "add" { shared.Debugf("Scheduler: %s: %s has been added: updating network priorities", e[0], e[1]) deviceNetworkPriority(d, e[1]) } case e := <-deviceSchedRebalance: if len(e) != 3 { shared.Log.Error("Scheduler: received an invalid rebalance event") continue } shared.Debugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2]) deviceTaskBalance(d) } } }
func pruneExpiredImages(d *Daemon) { shared.Debugf("Pruning expired images") expiry, err := d.ConfigValueGet("images.remote_cache_expiry") if err != nil { shared.Log.Error("Unable to read the images.remote_cache_expiry key") return } if expiry == "" { expiry = "10" } expiryInt, err := strconv.Atoi(expiry) if err != nil { shared.Log.Error("Invalid value for images.remote_cache_expiry", log.Ctx{"err": err}) return } images, err := dbImagesGetExpired(d.db, expiryInt) if err != nil { shared.Log.Error("Unable to retrieve the list of expired images", log.Ctx{"err": err}) return } for _, fp := range images { if err := doDeleteImage(d, fp); err != nil { shared.Log.Error("Error deleting image", log.Ctx{"err": err, "fp": fp}) } } shared.Debugf("Done pruning expired images") }
func containersPost(d *Daemon, r *http.Request) Response { shared.Debugf("Responding to container create") req := containerPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } if req.Name == "" { req.Name = strings.ToLower(petname.Generate(2, "-")) shared.Debugf("No name provided, creating %s", req.Name) } if strings.Contains(req.Name, shared.SnapshotDelimiter) { return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter)) } switch req.Source.Type { case "image": return createFromImage(d, &req) case "none": return createFromNone(d, &req) case "migration": return createFromMigration(d, &req) case "copy": return createFromCopy(d, &req) default: return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type)) } }
func containersPost(d *Daemon, r *http.Request) Response { shared.Debugf("responding to create") if d.IdmapSet == nil { return BadRequest(fmt.Errorf("shared's user has no subuids")) } req := containerPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } if req.Name == "" { req.Name = strings.ToLower(petname.Generate(2, "-")) shared.Debugf("no name provided, creating %s", req.Name) } switch req.Source.Type { case "image": return createFromImage(d, &req) case "none": return createFromNone(d, &req) case "migration": return createFromMigration(d, &req) case "copy": return createFromCopy(d, &req) default: return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type)) } }
func imageDelete(d *Daemon, r *http.Request) Response { fingerprint := mux.Vars(r)["fingerprint"] imgInfo, err := dbImageGet(d.db, fingerprint, false) if err != nil { return SmartError(err) } fname := shared.VarPath("images", imgInfo.Fingerprint) err = os.Remove(fname) if err != nil { shared.Debugf("Error deleting image file %s: %s\n", fname, err) } fmetaname := shared.VarPath("images", imgInfo.Fingerprint+".rootfs") if shared.PathExists(fmetaname) { err = os.Remove(fmetaname) if err != nil { shared.Debugf("Error deleting image file %s: %s\n", fmetaname, err) } } vgname, vgnameIsSet, err := getServerConfigValue(d, "core.lvm_vg_name") if err != nil { return InternalError(fmt.Errorf("Error checking server config: %v", err)) } if vgnameIsSet { err = shared.LVMRemoveLV(vgname, imgInfo.Fingerprint) if err != nil { return InternalError(fmt.Errorf("Failed to remove deleted image LV: %v", err)) } lvsymlink := fmt.Sprintf("%s.lv", fname) err = os.Remove(lvsymlink) if err != nil { return InternalError(fmt.Errorf("Failed to remove symlink to deleted image LV: '%s': %v", lvsymlink, err)) } } else if d.BackingFs == "btrfs" { subvol := fmt.Sprintf("%s.btrfs", fname) btrfsDeleteSubvol(subvol) } tx, err := dbBegin(d.db) if err != nil { return InternalError(err) } _, _ = tx.Exec("DELETE FROM images_aliases WHERE image_id=?", imgInfo.Id) _, _ = tx.Exec("DELETE FROM images_properties WHERE image_id?", imgInfo.Id) _, _ = tx.Exec("DELETE FROM images WHERE id=?", imgInfo.Id) if err := txCommit(tx); err != nil { return InternalError(err) } return EmptySyncResponse }
/* * Export the container to a unshifted tarfile containing: * dir/ * metadata.yaml * rootfs/ */ func (c *containerLXD) ExportToTar(snap string, w io.Writer) error { if snap == "" && c.IsRunning() { return fmt.Errorf("Cannot export a running container as image") } idmap, err := c.LastIdmapSetGet() if err != nil { return err } if idmap != nil { if err := idmap.UnshiftRootfs(c.RootfsPathGet()); err != nil { return err } defer idmap.ShiftRootfs(c.RootfsPathGet()) } tw := tar.NewWriter(w) // keep track of the first path we saw for each path with nlink>1 linkmap := map[uint64]string{} cDir := c.PathGet("") // Path inside the tar image is the pathname starting after cDir offset := len(cDir) + 1 writeToTar := func(path string, fi os.FileInfo, err error) error { if err := c.tarStoreFile(linkmap, offset, tw, path, fi); err != nil { shared.Debugf("Error tarring up %s: %s", path, err) return err } return nil } fnam := filepath.Join(cDir, "metadata.yaml") if shared.PathExists(fnam) { fi, err := os.Lstat(fnam) if err != nil { shared.Debugf("Error statting %s during exportToTar", fnam) tw.Close() return err } if err := c.tarStoreFile(linkmap, offset, tw, fnam, fi); err != nil { shared.Debugf("Error writing to tarfile: %s", err) tw.Close() return err } } fnam = filepath.Join(cDir, "rootfs") filepath.Walk(fnam, writeToTar) fnam = filepath.Join(cDir, "templates") if shared.PathExists(fnam) { filepath.Walk(fnam, writeToTar) } return tw.Close() }
func activateIfNeeded() error { // Don't start a full daemon, we just need DB access d := &Daemon{ IsMock: false, imagesDownloading: map[string]chan bool{}, imagesDownloadingLock: sync.RWMutex{}, } err := initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } // Look for network socket value, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if value != "" { shared.Debugf("Daemon has core.https_address set, activating...") _, err := lxd.NewClient(&lxd.DefaultConfig, "local") return err } // Look for auto-started or previously started containers d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { return err } result, err := dbContainersList(d.db, cTypeRegular) if err != nil { return err } for _, name := range result { c, err := containerLoadByName(d, name) if err != nil { return err } config := c.ExpandedConfig() lastState := config["volatile.last_state.power"] autoStart := config["boot.autostart"] if lastState == "RUNNING" || lastState == "Running" || autoStart == "true" { shared.Debugf("Daemon has auto-started containers, activating...") _, err := lxd.NewClient(&lxd.DefaultConfig, "local") return err } } shared.Debugf("No need to start the daemon now.") return nil }
func (c *Client) Finger() error { shared.Debugf("Fingering the daemon") _, err := c.GetServerConfig() if err != nil { return err } shared.Debugf("Pong received") return nil }
func (d *Daemon) CheckTrustState(cert x509.Certificate) bool { for k, v := range d.clientCerts { if bytes.Compare(cert.Raw, v.Raw) == 0 { shared.Debugf("found cert for %s", k) return true } shared.Debugf("client cert != key for %s", k) } return false }
func containerReplaceConfig(d *Daemon, ct *lxdContainer, name string, newConfig containerConfigReq) error { /* check to see that the config actually applies to the container * successfully before saving it. in particular, raw.lxc and * raw.apparmor need to be parsed once to make sure they make sense. */ if err := ct.applyConfig(newConfig.Config, false); err != nil { return err } tx, err := shared.DbBegin(d.db) if err != nil { return err } /* Update config or profiles */ if err = dbClearContainerConfig(tx, ct.id); err != nil { shared.Debugf("Error clearing configuration for container %s\n", name) tx.Rollback() return err } if err = dbInsertContainerConfig(tx, ct.id, newConfig.Config); err != nil { shared.Debugf("Error inserting configuration for container %s\n", name) tx.Rollback() return err } /* handle profiles */ if emptyProfile(newConfig.Profiles) { _, err := tx.Exec("DELETE from containers_profiles where container_id=?", ct.id) if err != nil { tx.Rollback() return err } } else { if err := dbInsertProfiles(tx, ct.id, newConfig.Profiles); err != nil { tx.Rollback() return err } } err = AddDevices(tx, "container", ct.id, newConfig.Devices) if err != nil { tx.Rollback() return err } return shared.TxCommit(tx) }
func btrfsDeleteSubvol(subvol string) error { if err := btrfsCmdIsInstalled(); err != nil { return err } output, err := exec.Command("btrfs", "subvolume", "delete", subvol).CombinedOutput() if err != nil { shared.Debugf("btrfs subvolume delete %s failed\n", subvol) shared.Debugf(string(output)) return err } return nil }
func (op *operation) Run() (chan error, error) { if op.status != shared.Pending { return nil, fmt.Errorf("Only pending operations can be started") } chanRun := make(chan error, 1) op.lock.Lock() op.status = shared.Running if op.onRun != nil { go func(op *operation, chanRun chan error) { err := op.onRun(op) if err != nil { op.lock.Lock() op.status = shared.Failure op.err = SmartError(err).String() op.lock.Unlock() op.done() chanRun <- err shared.Debugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err) _, md, _ := op.Render() eventSend("operation", md) return } op.lock.Lock() op.status = shared.Success op.lock.Unlock() op.done() chanRun <- nil op.lock.Lock() shared.Debugf("Success for %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) op.lock.Unlock() }(op, chanRun) } op.lock.Unlock() shared.Debugf("Started %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return chanRun, nil }
func txCommit(tx *sql.Tx) error { for { err := tx.Commit() if err == nil { return nil } if !isDbLockedError(err) { shared.Debugf("Txcommit: error %q\n", err) return err } shared.Debugf("Txcommit: db was locked\n") shared.PrintStack() time.Sleep(1 * time.Second) } }
func makeBtrfsSubvol(imagefname, subvol string) error { output, err := exec.Command("btrfs", "subvolume", "create", subvol).CombinedOutput() if err != nil { shared.Debugf("btrfs subvolume creation failed\n") shared.Debugf(string(output)) return err } err = untarImage(imagefname, subvol) if err != nil { return err } return nil }
func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interface{}) error { for { err := db.QueryRow(q, args...).Scan(outargs...) if err == nil { return nil } if !isDbLockedError(err) { shared.Debugf("DbQuery: query %q error %q\n", q, err) return err } shared.Debugf("DbQueryRowScan: query %q args %q, DB was locked\n", q, args) shared.PrintStack() time.Sleep(1 * time.Second) } }
/* * . q is the database query * . inargs is an array of interfaces containing the query arguments * . outfmt is an array of interfaces containing the right types of output * arguments, i.e. * var arg1 string * var arg2 int * outfmt := {}interface{}{arg1, arg2} * * The result will be an array (one per output row) of arrays (one per output argument) * of interfaces, containing pointers to the actual output arguments. */ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) { for { result, err := doDbQueryScan(db, q, inargs, outfmt) if err == nil { return result, nil } if !isDbLockedError(err) { shared.Debugf("DbQuery: query %q error %q\n", q, err) return nil, err } shared.Debugf("DbQueryscan: query %q inargs %q, DB was locked\n", q, inargs) shared.PrintStack() time.Sleep(1 * time.Second) } }
func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) { for { result, err := db.Exec(q, args...) if err == nil { return result, nil } if !isDbLockedError(err) { shared.Debugf("DbExec: query %q error %q\n", q, err) return nil, err } shared.Debugf("DbExec: query %q args %q, DB was locked\n", q, args) shared.PrintStack() time.Sleep(1 * time.Second) } }