[lxc-devel] [lxd/master] Directory api
tych0 on Github
lxc-bot at linuxcontainers.org
Wed Sep 7 22:49:24 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160907/fc116882/attachment.bin>
-------------- next part --------------
From 65029e4c85450fb3624ff61f611ef10ab89522fa Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 19 Aug 2016 17:47:01 +0000
Subject: [PATCH 1/2] initial implementation of recursive file push/pull
Closes #1218
Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
client.go | 140 +++++++++++++++++++++++++++++++++++++++++++++--
lxc/file.go | 70 ++++++++++++++++++++----
lxd/container.go | 2 +-
lxd/container_file.go | 81 ++++++++++++++++-----------
lxd/container_lxc.go | 36 ++++++++----
lxd/nsexec.go | 75 +++++++++++++++++++++----
lxd/response.go | 11 ++++
shared/util.go | 12 +++-
test/suites/filemanip.sh | 12 ++++
9 files changed, 365 insertions(+), 74 deletions(-)
diff --git a/client.go b/client.go
index 8747949..d2bc343 100644
--- a/client.go
+++ b/client.go
@@ -96,6 +96,15 @@ func (r *Response) MetadataAsOperation() (*shared.Operation, error) {
return &op, nil
}
+func (r *Response) MetadataAsStringSlice() ([]string, error) {
+ sl := []string{}
+ if err := json.Unmarshal(r.Metadata, &sl); err != nil {
+ return nil, err
+ }
+
+ return sl, nil
+}
+
// ParseResponse parses a lxd style response out of an http.Response. Note that
// this does _not_ automatically convert error responses to golang errors. To
// do that, use ParseError. Internal client library uses should probably use
@@ -1771,6 +1780,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
return err
}
req.Header.Set("User-Agent", shared.UserAgent)
+ req.Header.Set("X-LXD-Type", "file")
if mode != "" {
req.Header.Set("X-LXD-mode", mode)
@@ -1791,9 +1801,68 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
return err
}
-func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadCloser, error) {
+func (c *Client) Mkdir(container string, p string, mode os.FileMode) error {
+ if c.Remote.Public {
+ return fmt.Errorf("This function isn't supported by public remotes.")
+ }
+
+ query := url.Values{"path": []string{p}}
+ uri := c.url(shared.APIVersion, "containers", container, "files") + "?" + query.Encode()
+
+ req, err := http.NewRequest("POST", uri, nil)
+ if err != nil {
+ return err
+ }
+
+ req.Header.Set("User-Agent", shared.UserAgent)
+ req.Header.Set("X-LXD-type", "directory")
+ req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", mode.Perm()))
+
+ raw, err := c.Http.Do(req)
+ if err != nil {
+ return err
+ }
+
+ _, err = HoistResponse(raw, Sync)
+ return err
+}
+
+func (c *Client) RecursivePushFile(container string, source string, target string) error {
+ if c.Remote.Public {
+ return fmt.Errorf("This function isn't supported by public remotes.")
+ }
+
+ sourceDir := filepath.Dir(source)
+
+ sendFile := func(p string, fInfo os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("got error sending path %s: %s", p, err)
+ }
+
+ targetPath := path.Join(target, p[len(sourceDir):])
+ if fInfo.IsDir() {
+ return c.Mkdir(container, targetPath, fInfo.Mode())
+ }
+
+ f, err := os.Open(p)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ mode := fInfo.Mode()
+ uid := int(fInfo.Sys().(*syscall.Stat_t).Uid)
+ gid := int(fInfo.Sys().(*syscall.Stat_t).Gid)
+
+ return c.PushFile(container, targetPath, gid, uid, fmt.Sprintf("0%o", mode), f)
+ }
+
+ return filepath.Walk(source, sendFile)
+}
+
+func (c *Client) PullFile(container string, p string) (int, int, int, string, io.ReadCloser, []string, error) {
if c.Remote.Public {
- return 0, 0, 0, nil, fmt.Errorf("This function isn't supported by public remotes.")
+ return 0, 0, 0, "", nil, nil, fmt.Errorf("This function isn't supported by public remotes.")
}
uri := c.url(shared.APIVersion, "containers", container, "files")
@@ -1801,12 +1870,73 @@ func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadClo
r, err := c.getRaw(uri + "?" + query.Encode())
if err != nil {
- return 0, 0, 0, nil, err
+ return 0, 0, 0, "", nil, nil, err
+ }
+
+ uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
+ if type_ == "directory" {
+ resp, err := HoistResponse(r, Sync)
+ if err != nil {
+ return 0, 0, 0, "", nil, nil, err
+ }
+
+ entries, err := resp.MetadataAsStringSlice()
+ if err != nil {
+ return 0, 0, 0, "", nil, nil, err
+ }
+
+ return uid, gid, mode, type_, nil, entries, nil
+ } else if type_ == "file" {
+ return uid, gid, mode, type_, r.Body, nil, nil
+ } else {
+ return 0, 0, 0, "", nil, nil, fmt.Errorf("unknown file type '%s'", type_)
+ }
+}
+
+func (c *Client) RecursivePullFile(container string, p string, targetDir string) error {
+ if c.Remote.Public {
+ return fmt.Errorf("This function isn't supported by public remotes.")
+ }
+
+ _, _, mode, type_, buf, entries, err := c.PullFile(container, p)
+ if err != nil {
+ return err
}
- uid, gid, mode := shared.ParseLXDFileHeaders(r.Header)
+ target := filepath.Join(targetDir, filepath.Base(p))
- return uid, gid, mode, r.Body, nil
+ if type_ == "directory" {
+ if err := os.Mkdir(target, os.FileMode(mode)); err != nil {
+ return err
+ }
+
+ for _, ent := range entries {
+ nextP := path.Join(p, ent)
+ if err := c.RecursivePullFile(container, nextP, target); err != nil {
+ return err
+ }
+ }
+ } else if type_ == "file" {
+ f, err := os.Create(target)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ err = f.Chmod(os.FileMode(mode))
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(f, buf)
+ if err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("unknown file type '%s'", type_)
+ }
+
+ return nil
}
func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
diff --git a/lxc/file.go b/lxc/file.go
index 40d5994..c8b56a8 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -22,6 +22,8 @@ type fileCmd struct {
uid int
gid int
mode string
+
+ recursive bool
}
func (c *fileCmd) showByDefault() bool {
@@ -32,17 +34,28 @@ func (c *fileCmd) usage() string {
return i18n.G(
`Manage files on a container.
-lxc file pull <source> [<source>...] <target>
-lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>
+lxc file pull [-r|--recursive] <source> [<source>...] <target>
+lxc file push [-r|--recursive] [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>
lxc file edit <file>
-<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>`)
+<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>
+
+Examples:
+
+To push /etc/hosts into the container foo:
+ lxc file push /etc/hosts foo/etc/hosts
+
+To pull /etc/hosts from the container:
+ lxc file pull foo/etc/hosts .
+`)
}
func (c *fileCmd) flags() {
gnuflag.IntVar(&c.uid, "uid", -1, i18n.G("Set the file's uid on push"))
gnuflag.IntVar(&c.gid, "gid", -1, i18n.G("Set the file's gid on push"))
gnuflag.StringVar(&c.mode, "mode", "", i18n.G("Set the file's perms on push"))
+ gnuflag.BoolVar(&c.recursive, "recusrive", false, i18n.G("Recursively push or pull files"))
+ gnuflag.BoolVar(&c.recursive, "r", false, i18n.G("Recursively push or pull files"))
}
func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string) error {
@@ -65,6 +78,27 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
return err
}
+ var sourcefilenames []string
+ for _, fname := range args[:len(args)-1] {
+ if !strings.HasPrefix(fname, "--") {
+ sourcefilenames = append(sourcefilenames, fname)
+ }
+ }
+
+ if c.recursive {
+ if c.uid != -1 || c.gid != -1 || c.mode != "" {
+ return fmt.Errorf(i18n.G("can't supply uid/gid/mode in recursive mode"))
+ }
+
+ for _, fname := range sourcefilenames {
+ if err := d.RecursivePushFile(container, fname, pathSpec[1]); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+
mode := os.FileMode(0755)
if c.mode != "" {
if len(c.mode) == 3 {
@@ -90,13 +124,6 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
_, targetfilename := filepath.Split(targetPath)
- var sourcefilenames []string
- for _, fname := range args[:len(args)-1] {
- if !strings.HasPrefix(fname, "--") {
- sourcefilenames = append(sourcefilenames, fname)
- }
- }
-
if (targetfilename != "") && (len(sourcefilenames) > 1) {
return errArgs
}
@@ -201,11 +228,23 @@ func (c *fileCmd) pull(config *lxd.Config, args []string) error {
return err
}
- _, _, _, buf, err := d.PullFile(container, pathSpec[1])
+ if c.recursive {
+ if err := d.RecursivePullFile(container, pathSpec[1], target); err != nil {
+ return err
+ }
+
+ continue
+ }
+
+ _, _, mode, type_, buf, _, err := d.PullFile(container, pathSpec[1])
if err != nil {
return err
}
+ if type_ == "directory" {
+ return fmt.Errorf(i18n.G("can't pull a directory without --recursive"))
+ }
+
var targetPath string
if targetIsDir {
targetPath = path.Join(target, path.Base(pathSpec[1]))
@@ -222,6 +261,11 @@ func (c *fileCmd) pull(config *lxd.Config, args []string) error {
return err
}
defer f.Close()
+
+ err = f.Chmod(os.FileMode(mode))
+ if err != nil {
+ return err
+ }
}
_, err = io.Copy(f, buf)
@@ -238,6 +282,10 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
return errArgs
}
+ if c.recursive {
+ return fmt.Errorf(i18n.G("recursive edit doesn't make sense :("))
+ }
+
// If stdin isn't a terminal, read text from it
if !termios.IsTerminal(int(syscall.Stdin)) {
return c.push(config, false, append([]string{os.Stdin.Name()}, args[0]))
diff --git a/lxd/container.go b/lxd/container.go
index e2a0980..87c78dd 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -319,7 +319,7 @@ type container interface {
ConfigKeySet(key string, value string) error
// File handling
- FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error)
+ FilePull(srcpath string, dstpath string) (int, int, os.FileMode, string, []string, error)
FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error
// Command execution
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7ac2085..e682061 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -49,8 +49,8 @@ func containerFileGet(c container, path string, r *http.Request) Response {
}
defer temp.Close()
- // Pul the file from the container
- uid, gid, mode, err := c.FilePull(path, temp.Name())
+ // Pull the file from the container
+ uid, gid, mode, type_, dirEnts, err := c.FilePull(path, temp.Name())
if err != nil {
return SmartError(err)
}
@@ -59,41 +59,58 @@ func containerFileGet(c container, path string, r *http.Request) Response {
"X-LXD-uid": fmt.Sprintf("%d", uid),
"X-LXD-gid": fmt.Sprintf("%d", gid),
"X-LXD-mode": fmt.Sprintf("%04o", mode),
+ "X-LXD-type": type_,
}
- // Make a file response struct
- files := make([]fileResponseEntry, 1)
- files[0].identifier = filepath.Base(path)
- files[0].path = temp.Name()
- files[0].filename = filepath.Base(path)
-
- return FileResponse(r, files, headers, true)
+ if type_ == "file" {
+ // Make a file response struct
+ files := make([]fileResponseEntry, 1)
+ files[0].identifier = filepath.Base(path)
+ files[0].path = temp.Name()
+ files[0].filename = filepath.Base(path)
+
+ return FileResponse(r, files, headers, true)
+ } else if type_ == "directory" {
+ return SyncResponseHeaders(true, dirEnts, headers)
+ } else {
+ return InternalError(fmt.Errorf("bad file type %s", type_))
+ }
}
func containerFilePut(c container, path string, r *http.Request) Response {
// Extract file ownership and mode from headers
- uid, gid, mode := shared.ParseLXDFileHeaders(r.Header)
-
- // Write file content to a tempfile
- temp, err := ioutil.TempFile("", "lxd_forkputfile_")
- if err != nil {
- return InternalError(err)
- }
- defer func() {
- temp.Close()
- os.Remove(temp.Name())
- }()
-
- _, err = io.Copy(temp, r.Body)
- if err != nil {
- return InternalError(err)
+ uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
+
+ if type_ == "file" {
+ // Write file content to a tempfile
+ temp, err := ioutil.TempFile("", "lxd_forkputfile_")
+ if err != nil {
+ return InternalError(err)
+ }
+ defer func() {
+ temp.Close()
+ os.Remove(temp.Name())
+ }()
+
+ _, err = io.Copy(temp, r.Body)
+ if err != nil {
+ return InternalError(err)
+ }
+
+ // Transfer the file into the container
+ err = c.FilePush(temp.Name(), path, uid, gid, mode)
+ if err != nil {
+ return InternalError(err)
+ }
+
+ return EmptySyncResponse
+ } else if type_ == "directory" {
+ err := c.FilePush("", path, uid, gid, mode)
+ if err != nil {
+ return InternalError(err)
+ }
+ return EmptySyncResponse
+ } else {
+ return InternalError(fmt.Errorf("bad file type %s", type_))
}
-
- // Transfer the file into the container
- err = c.FilePush(temp.Name(), path, uid, gid, mode)
- if err != nil {
- return InternalError(err)
- }
-
- return EmptySyncResponse
}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index aeac665..e12dbc8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3163,12 +3163,12 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
return nil
}
-func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error) {
+func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.FileMode, string, []string, error) {
// Setup container storage if needed
if !c.IsRunning() {
err := c.StorageStart()
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
}
@@ -3186,13 +3186,15 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
if !c.IsRunning() {
err := c.StorageStop()
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
}
uid := -1
gid := -1
mode := -1
+ type_ := "unknown"
+ var dirEnts []string
var errStr string
@@ -3211,16 +3213,16 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
if strings.HasPrefix(line, "errno: ") {
errno := strings.TrimPrefix(line, "errno: ")
if errno == "2" {
- return -1, -1, 0, os.ErrNotExist
+ return -1, -1, 0, "", nil, os.ErrNotExist
}
- return -1, -1, 0, fmt.Errorf(errStr)
+ return -1, -1, 0, "", nil, fmt.Errorf(errStr)
}
// Extract the uid
if strings.HasPrefix(line, "uid: ") {
uid, err = strconv.Atoi(strings.TrimPrefix(line, "uid: "))
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
continue
@@ -3230,7 +3232,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
if strings.HasPrefix(line, "gid: ") {
gid, err = strconv.Atoi(strings.TrimPrefix(line, "gid: "))
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
continue
@@ -3240,17 +3242,29 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
if strings.HasPrefix(line, "mode: ") {
mode, err = strconv.Atoi(strings.TrimPrefix(line, "mode: "))
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
continue
}
+ if strings.HasPrefix(line, "type: ") {
+ type_ = strings.TrimPrefix(line, "type: ")
+ continue
+ }
+
+ if strings.HasPrefix(line, "entry: ") {
+ ent := strings.TrimPrefix(line, "entry: ")
+ ent = strings.Replace(ent, "\x00", "\n", -1)
+ dirEnts = append(dirEnts, ent)
+ continue
+ }
+
shared.Debugf("forkgetfile: %s", line)
}
if err != nil {
- return -1, -1, 0, fmt.Errorf(
+ return -1, -1, 0, "", nil, fmt.Errorf(
"Error calling 'lxd forkgetfile %s %d %s': err='%v'",
dstpath,
c.InitPID(),
@@ -3261,14 +3275,14 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
// Unmap uid and gid if needed
idmapset, err := c.LastIdmapSet()
if err != nil {
- return -1, -1, 0, err
+ return -1, -1, 0, "", nil, err
}
if idmapset != nil {
uid, gid = idmapset.ShiftFromNs(uid, gid)
}
- return uid, gid, os.FileMode(mode), nil
+ return uid, gid, os.FileMode(mode), type_, dirEnts, nil
}
func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error {
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index 30e2d1a..edd0bbf 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -37,6 +37,7 @@ package main
#include <alloca.h>
#include <libgen.h>
#include <ifaddrs.h>
+#include <dirent.h>
// This expects:
// ./lxd forkputfile /source/path <pid> /target/path
@@ -126,22 +127,21 @@ int dosetns(int pid, char *nstype) {
}
int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode) {
- int host_fd, container_fd;
+ int host_fd = -1, container_fd = -1;
int ret = -1;
int container_open_flags;
struct stat st;
int exists = 1;
+ bool is_dir_manip = !strcmp(host, "");
- host_fd = open(host, O_RDWR);
- if (host_fd < 0) {
- error("error: open");
- return -1;
+ if (!is_dir_manip) {
+ host_fd = open(host, O_RDWR);
+ if (host_fd < 0) {
+ error("error: open");
+ return -1;
+ }
}
- container_open_flags = O_RDWR;
- if (is_put)
- container_open_flags |= O_CREAT;
-
if (pid > 0) {
if (dosetns(pid, "mnt") < 0) {
error("error: setns");
@@ -159,9 +159,25 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
}
}
- if (is_put && stat(container, &st) < 0)
+ if (is_put && is_dir_manip) {
+ if (mkdir(container, mode) < 0 && errno != EEXIST) {
+ error("error: mkdir");
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (stat(container, &st) < 0)
exists = 0;
+ container_open_flags = O_RDWR;
+ if (is_put)
+ container_open_flags |= O_CREAT;
+
+ if (exists && S_ISDIR(st.st_mode))
+ container_open_flags = O_DIRECTORY;
+
umask(0);
container_fd = open(container, container_open_flags, 0);
if (container_fd < 0) {
@@ -198,10 +214,8 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
error("error: chown");
goto close_container;
}
-
ret = 0;
} else {
- ret = copy(host_fd, container_fd);
if (fstat(container_fd, &st) < 0) {
error("error: stat");
@@ -211,6 +225,43 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
fprintf(stderr, "uid: %ld\n", (long)st.st_uid);
fprintf(stderr, "gid: %ld\n", (long)st.st_gid);
fprintf(stderr, "mode: %ld\n", (unsigned long)st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+ if (S_ISDIR(st.st_mode)) {
+ DIR *fdir;
+ struct dirent *de;
+
+ fdir = fdopendir(container_fd);
+ if (!fdir) {
+ error("error: fdopendir");
+ goto close_container;
+ }
+
+ fprintf(stderr, "type: directory\n");
+
+ while((de = readdir(fdir))) {
+ int len, i;
+
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+
+ fprintf(stderr, "entry: ");
+
+ // swap \n to \0 since we split this output by line
+ for (i = 0, len = strlen(de->d_name); i < len; i++) {
+ if (*(de->d_name + i) == '\n')
+ putc(0, stderr);
+ else
+ putc(*(de->d_name + i), stderr);
+ }
+ fprintf(stderr, "\n");
+ }
+
+ // container_fd is dead now that we fopendir'd it
+ goto close_host;
+ } else {
+ fprintf(stderr, "type: file\n");
+ ret = copy(host_fd, container_fd);
+ }
+ fprintf(stderr, "type: %s", S_ISDIR(st.st_mode) ? "directory" : "file");
}
close_container:
diff --git a/lxd/response.go b/lxd/response.go
index fc37f9c..1116857 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -42,6 +42,7 @@ type syncResponse struct {
etag interface{}
metadata interface{}
location string
+ headers map[string]string
}
func (r *syncResponse) Render(w http.ResponseWriter) error {
@@ -59,6 +60,12 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
status = shared.Failure
}
+ if r.headers != nil {
+ for h, v := range r.headers {
+ w.Header().Set(h, v)
+ }
+ }
+
if r.location != "" {
w.Header().Set("Location", r.location)
w.WriteHeader(201)
@@ -88,6 +95,10 @@ func SyncResponseLocation(success bool, metadata interface{}, location string) R
return &syncResponse{success: success, metadata: metadata, location: location}
}
+func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]string) Response {
+ return &syncResponse{success: success, metadata: metadata, headers: headers}
+}
+
var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
// File transfer response
diff --git a/shared/util.go b/shared/util.go
index b6539c2..a40874f 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -106,7 +106,7 @@ func LogPath(path ...string) string {
return filepath.Join(items...)
}
-func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int) {
+func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int, type_ string) {
uid, err := strconv.Atoi(headers.Get("X-LXD-uid"))
if err != nil {
uid = -1
@@ -127,7 +127,15 @@ func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int) {
}
}
- return uid, gid, mode
+ type_ = headers.Get("X-LXD-type")
+ /* backwards compat: before "type" was introduced, we could only
+ * manipulate files
+ */
+ if type_ == "" {
+ type_ = "file"
+ }
+
+ return uid, gid, mode, type_
}
func ReadToJSON(r io.Reader, req interface{}) error {
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 07ca148..2282edc 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -15,5 +15,17 @@ test_filemanip() {
err=$(my_curl -o /dev/null -w "%{http_code}" -X GET "https://${LXD_ADDR}/1.0/containers/filemanip/files?path=/tmp/foo")
[ "${err}" -eq "404" ]
+ # lxc {push|pull} -r
+ mkdir "${TEST_DIR}"/source
+ echo "foo" > "${TEST_DIR}"/source/foo
+ echo "bar" > "${TEST_DIR}"/source/bar
+
+ lxc file push -r "${TEST_DIR}"/source filemanip/tmp
+ mkdir "${TEST_DIR}"/dest
+ lxc file pull -r filemanip/tmp/source "${TEST_DIR}"/dest
+
+ [ "$(cat "${TEST_DIR}"/dest/source/foo)" = "foo" ]
+ [ "$(cat "${TEST_DIR}"/dest/source/bar)" = "bar" ]
+
lxc delete filemanip -f
}
From 510b038ab80b67e4e930327f72d216028361de56 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 7 Sep 2016 22:46:29 +0000
Subject: [PATCH 2/2] implement `lxc file push -p` to enable creating
directories
Closes #2290
Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
client.go | 33 +++++++++++++++++++++++++++++++++
lxc/file.go | 44 ++++++++++++++++++++++++++++++--------------
test/suites/filemanip.sh | 5 +++++
3 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/client.go b/client.go
index d2bc343..c869fbb 100644
--- a/client.go
+++ b/client.go
@@ -1827,6 +1827,39 @@ func (c *Client) Mkdir(container string, p string, mode os.FileMode) error {
return err
}
+func (c *Client) MkdirP(container string, p string, mode os.FileMode) error {
+ if c.Remote.Public {
+ return fmt.Errorf("This function isn't supported by public remotes.")
+ }
+
+ parts := strings.Split(p, "/")
+ i := len(parts)
+
+ for ; i >= 1; i-- {
+ cur := filepath.Join(parts[:i]...)
+ _, _, _, type_, _, _, err := c.PullFile(container, cur)
+ if err != nil {
+ continue
+ }
+
+ if type_ != "directory" {
+ return fmt.Errorf("%s is not a directory", cur)
+ }
+
+ i++
+ break
+ }
+
+ for ; i <= len(parts); i++ {
+ cur := filepath.Join(parts[:i]...)
+ if err := c.Mkdir(container, cur, mode); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func (c *Client) RecursivePushFile(container string, source string, target string) error {
if c.Remote.Public {
return fmt.Errorf("This function isn't supported by public remotes.")
diff --git a/lxc/file.go b/lxc/file.go
index c8b56a8..f60c719 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -24,6 +24,8 @@ type fileCmd struct {
mode string
recursive bool
+
+ mkdirs bool
}
func (c *fileCmd) showByDefault() bool {
@@ -35,7 +37,7 @@ func (c *fileCmd) usage() string {
`Manage files on a container.
lxc file pull [-r|--recursive] <source> [<source>...] <target>
-lxc file push [-r|--recursive] [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>
+lxc file push [-r|--recursive] [-p|create-dirs] [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>
lxc file edit <file>
<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>
@@ -56,6 +58,8 @@ func (c *fileCmd) flags() {
gnuflag.StringVar(&c.mode, "mode", "", i18n.G("Set the file's perms on push"))
gnuflag.BoolVar(&c.recursive, "recusrive", false, i18n.G("Recursively push or pull files"))
gnuflag.BoolVar(&c.recursive, "r", false, i18n.G("Recursively push or pull files"))
+ gnuflag.BoolVar(&c.mkdirs, "create-dirs", false, i18n.G("Create any directories necessary"))
+ gnuflag.BoolVar(&c.mkdirs, "p", false, i18n.G("Create any directories necessary"))
}
func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string) error {
@@ -85,12 +89,31 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
}
}
+ mode := os.FileMode(0755)
+ if c.mode != "" {
+ if len(c.mode) == 3 {
+ c.mode = "0" + c.mode
+ }
+
+ m, err := strconv.ParseInt(c.mode, 0, 0)
+ if err != nil {
+ return err
+ }
+ mode = os.FileMode(m)
+ }
+
if c.recursive {
if c.uid != -1 || c.gid != -1 || c.mode != "" {
return fmt.Errorf(i18n.G("can't supply uid/gid/mode in recursive mode"))
}
for _, fname := range sourcefilenames {
+ if c.mkdirs {
+ if err := d.MkdirP(container, fname, mode); err != nil {
+ return err
+ }
+ }
+
if err := d.RecursivePushFile(container, fname, pathSpec[1]); err != nil {
return err
}
@@ -99,19 +122,6 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
return nil
}
- mode := os.FileMode(0755)
- if c.mode != "" {
- if len(c.mode) == 3 {
- c.mode = "0" + c.mode
- }
-
- m, err := strconv.ParseInt(c.mode, 0, 0)
- if err != nil {
- return err
- }
- mode = os.FileMode(m)
- }
-
uid := 0
if c.uid >= 0 {
uid = c.uid
@@ -152,6 +162,12 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
fpath = path.Join(fpath, path.Base(f.Name()))
}
+ if c.mkdirs {
+ if err := d.MkdirP(container, filepath.Dir(fpath), mode); err != nil {
+ return err
+ }
+ }
+
if send_file_perms {
if c.mode == "" || c.uid == -1 || c.gid == -1 {
fMode, fUid, fGid, err := c.getOwner(f)
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 2282edc..6fb51d1 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -27,5 +27,10 @@ test_filemanip() {
[ "$(cat "${TEST_DIR}"/dest/source/foo)" = "foo" ]
[ "$(cat "${TEST_DIR}"/dest/source/bar)" = "bar" ]
+ lxc file push -p "${TEST_DIR}"/source/foo filemanip/tmp/this/is/a/nonexistent/directory/
+ echo done with push
+ lxc file pull filemanip/tmp/this/is/a/nonexistent/directory/foo "${TEST_DIR}"
+ [ "$(cat "${TEST_DIR}"/foo)" = "foo" ]
+
lxc delete filemanip -f
}
More information about the lxc-devel
mailing list