| // +build !windows |
| |
| package remotefs |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "encoding/json" |
| "io" |
| "os" |
| "path/filepath" |
| "strconv" |
| |
| "github.com/docker/docker/pkg/archive" |
| "github.com/docker/docker/pkg/symlink" |
| "github.com/sirupsen/logrus" |
| "golang.org/x/sys/unix" |
| ) |
| |
| // Func is the function definition for a generic remote fs function |
| // The input to the function is any serialized structs / data from in and the string slice |
| // from args. The output of the function will be serialized and written to out. |
| type Func func(stdin io.Reader, stdout io.Writer, args []string) error |
| |
| // Commands provide a string -> remotefs function mapping. |
| // This is useful for commandline programs that will receive a string |
| // as the function to execute. |
| var Commands = map[string]Func{ |
| StatCmd: Stat, |
| LstatCmd: Lstat, |
| ReadlinkCmd: Readlink, |
| MkdirCmd: Mkdir, |
| MkdirAllCmd: MkdirAll, |
| RemoveCmd: Remove, |
| RemoveAllCmd: RemoveAll, |
| LinkCmd: Link, |
| SymlinkCmd: Symlink, |
| LchmodCmd: Lchmod, |
| LchownCmd: Lchown, |
| MknodCmd: Mknod, |
| MkfifoCmd: Mkfifo, |
| OpenFileCmd: OpenFile, |
| ReadFileCmd: ReadFile, |
| WriteFileCmd: WriteFile, |
| ReadDirCmd: ReadDir, |
| ResolvePathCmd: ResolvePath, |
| ExtractArchiveCmd: ExtractArchive, |
| ArchivePathCmd: ArchivePath, |
| } |
| |
| // Stat functions like os.Stat. |
| // Args: |
| // - args[0] is the path |
| // Out: |
| // - out = FileInfo object |
| func Stat(in io.Reader, out io.Writer, args []string) error { |
| return stat(in, out, args, os.Stat) |
| } |
| |
| // Lstat functions like os.Lstat. |
| // Args: |
| // - args[0] is the path |
| // Out: |
| // - out = FileInfo object |
| func Lstat(in io.Reader, out io.Writer, args []string) error { |
| return stat(in, out, args, os.Lstat) |
| } |
| |
| func stat(in io.Reader, out io.Writer, args []string, statfunc func(string) (os.FileInfo, error)) error { |
| if len(args) < 1 { |
| return ErrInvalid |
| } |
| |
| fi, err := statfunc(args[0]) |
| if err != nil { |
| return err |
| } |
| |
| info := FileInfo{ |
| NameVar: fi.Name(), |
| SizeVar: fi.Size(), |
| ModeVar: fi.Mode(), |
| ModTimeVar: fi.ModTime().UnixNano(), |
| IsDirVar: fi.IsDir(), |
| } |
| |
| buf, err := json.Marshal(info) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := out.Write(buf); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // Readlink works like os.Readlink |
| // In: |
| // - args[0] is path |
| // Out: |
| // - Write link result to out |
| func Readlink(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 1 { |
| return ErrInvalid |
| } |
| |
| l, err := os.Readlink(args[0]) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := out.Write([]byte(l)); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // Mkdir works like os.Mkdir |
| // Args: |
| // - args[0] is the path |
| // - args[1] is the permissions in octal (like 0755) |
| func Mkdir(in io.Reader, out io.Writer, args []string) error { |
| return mkdir(in, out, args, os.Mkdir) |
| } |
| |
| // MkdirAll works like os.MkdirAll. |
| // Args: |
| // - args[0] is the path |
| // - args[1] is the permissions in octal (like 0755) |
| func MkdirAll(in io.Reader, out io.Writer, args []string) error { |
| return mkdir(in, out, args, os.MkdirAll) |
| } |
| |
| func mkdir(in io.Reader, out io.Writer, args []string, mkdirFunc func(string, os.FileMode) error) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| |
| perm, err := strconv.ParseUint(args[1], 8, 32) |
| if err != nil { |
| return err |
| } |
| return mkdirFunc(args[0], os.FileMode(perm)) |
| } |
| |
| // Remove works like os.Remove |
| // Args: |
| // - args[0] is the path |
| func Remove(in io.Reader, out io.Writer, args []string) error { |
| return remove(in, out, args, os.Remove) |
| } |
| |
| // RemoveAll works like os.RemoveAll |
| // Args: |
| // - args[0] is the path |
| func RemoveAll(in io.Reader, out io.Writer, args []string) error { |
| return remove(in, out, args, os.RemoveAll) |
| } |
| |
| func remove(in io.Reader, out io.Writer, args []string, removefunc func(string) error) error { |
| if len(args) < 1 { |
| return ErrInvalid |
| } |
| return removefunc(args[0]) |
| } |
| |
| // Link works like os.Link |
| // Args: |
| // - args[0] = old path name (link source) |
| // - args[1] = new path name (link dest) |
| func Link(in io.Reader, out io.Writer, args []string) error { |
| return link(in, out, args, os.Link) |
| } |
| |
| // Symlink works like os.Symlink |
| // Args: |
| // - args[0] = old path name (link source) |
| // - args[1] = new path name (link dest) |
| func Symlink(in io.Reader, out io.Writer, args []string) error { |
| return link(in, out, args, os.Symlink) |
| } |
| |
| func link(in io.Reader, out io.Writer, args []string, linkfunc func(string, string) error) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| return linkfunc(args[0], args[1]) |
| } |
| |
| // Lchmod changes permission of the given file without following symlinks |
| // Args: |
| // - args[0] = path |
| // - args[1] = permission mode in octal (like 0755) |
| func Lchmod(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| |
| perm, err := strconv.ParseUint(args[1], 8, 32) |
| if err != nil { |
| return err |
| } |
| |
| path := args[0] |
| if !filepath.IsAbs(path) { |
| path, err = filepath.Abs(path) |
| if err != nil { |
| return err |
| } |
| } |
| return unix.Fchmodat(0, path, uint32(perm), unix.AT_SYMLINK_NOFOLLOW) |
| } |
| |
| // Lchown works like os.Lchown |
| // Args: |
| // - args[0] = path |
| // - args[1] = uid in base 10 |
| // - args[2] = gid in base 10 |
| func Lchown(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 3 { |
| return ErrInvalid |
| } |
| |
| uid, err := strconv.ParseInt(args[1], 10, 64) |
| if err != nil { |
| return err |
| } |
| |
| gid, err := strconv.ParseInt(args[2], 10, 64) |
| if err != nil { |
| return err |
| } |
| return os.Lchown(args[0], int(uid), int(gid)) |
| } |
| |
| // Mknod works like syscall.Mknod |
| // Args: |
| // - args[0] = path |
| // - args[1] = permission mode in octal (like 0755) |
| // - args[2] = major device number in base 10 |
| // - args[3] = minor device number in base 10 |
| func Mknod(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 4 { |
| return ErrInvalid |
| } |
| |
| perm, err := strconv.ParseUint(args[1], 8, 32) |
| if err != nil { |
| return err |
| } |
| |
| major, err := strconv.ParseInt(args[2], 10, 32) |
| if err != nil { |
| return err |
| } |
| |
| minor, err := strconv.ParseInt(args[3], 10, 32) |
| if err != nil { |
| return err |
| } |
| |
| dev := unix.Mkdev(uint32(major), uint32(minor)) |
| return unix.Mknod(args[0], uint32(perm), int(dev)) |
| } |
| |
| // Mkfifo creates a FIFO special file with the given path name and permissions |
| // Args: |
| // - args[0] = path |
| // - args[1] = permission mode in octal (like 0755) |
| func Mkfifo(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| |
| perm, err := strconv.ParseUint(args[1], 8, 32) |
| if err != nil { |
| return err |
| } |
| return unix.Mkfifo(args[0], uint32(perm)) |
| } |
| |
| // OpenFile works like os.OpenFile. To manage the file pointer state, |
| // this function acts as a single file "file server" with Read/Write/Close |
| // being serialized control codes from in. |
| // Args: |
| // - args[0] = path |
| // - args[1] = flag in base 10 |
| // - args[2] = permission mode in octal (like 0755) |
| func OpenFile(in io.Reader, out io.Writer, args []string) (err error) { |
| logrus.Debugf("OpenFile: %v", args) |
| |
| defer func() { |
| if err != nil { |
| logrus.Errorf("OpenFile: return is non-nil, so writing cmdFailed back: %v", err) |
| // error code will be serialized by the caller, so don't write it here |
| WriteFileHeader(out, &FileHeader{Cmd: CmdFailed}, nil) |
| } |
| }() |
| |
| if len(args) < 3 { |
| logrus.Errorf("OpenFile: Not enough parameters") |
| return ErrInvalid |
| } |
| |
| flag, err := strconv.ParseInt(args[1], 10, 32) |
| if err != nil { |
| logrus.Errorf("OpenFile: Invalid flag: %v", err) |
| return err |
| } |
| |
| perm, err := strconv.ParseUint(args[2], 8, 32) |
| if err != nil { |
| logrus.Errorf("OpenFile: Invalid permission: %v", err) |
| return err |
| } |
| |
| f, err := os.OpenFile(args[0], int(flag), os.FileMode(perm)) |
| if err != nil { |
| logrus.Errorf("OpenFile: Failed to open: %v", err) |
| return err |
| } |
| |
| // Signal the client that OpenFile succeeded |
| logrus.Debugf("OpenFile: Sending OK header") |
| if err := WriteFileHeader(out, &FileHeader{Cmd: CmdOK}, nil); err != nil { |
| return err |
| } |
| |
| for { |
| logrus.Debugf("OpenFile: reading header") |
| hdr, err := ReadFileHeader(in) |
| if err != nil { |
| logrus.Errorf("OpenFile: Failed to ReadFileHeader: %v", err) |
| return err |
| } |
| logrus.Debugf("OpenFile: Header: %+v", hdr) |
| |
| var buf []byte |
| switch hdr.Cmd { |
| case Read: |
| logrus.Debugf("OpenFile: Read command") |
| buf = make([]byte, hdr.Size, hdr.Size) |
| n, err := f.Read(buf) |
| logrus.Debugf("OpenFile: Issued a read for %d, got %d bytes and error %v", hdr.Size, n, err) |
| if err != nil { |
| logrus.Errorf("OpenFile: Read failed: %v", err) |
| return err |
| } |
| buf = buf[:n] |
| case Write: |
| logrus.Debugf("OpenFile: Write command") |
| if _, err := io.CopyN(f, in, int64(hdr.Size)); err != nil { |
| logrus.Errorf("OpenFile: Write CopyN() failed: %v", err) |
| return err |
| } |
| case Seek: |
| logrus.Debugf("OpenFile: Seek command") |
| seekHdr := &SeekHeader{} |
| if err := binary.Read(in, binary.BigEndian, seekHdr); err != nil { |
| logrus.Errorf("OpenFile: Seek Read() failed: %v", err) |
| return err |
| } |
| res, err := f.Seek(seekHdr.Offset, int(seekHdr.Whence)) |
| if err != nil { |
| logrus.Errorf("OpenFile: Seek Seek() failed: %v", err) |
| return err |
| } |
| buffer := &bytes.Buffer{} |
| if err := binary.Write(buffer, binary.BigEndian, res); err != nil { |
| logrus.Errorf("OpenFile: Seek Write() failed: %v", err) |
| return err |
| } |
| buf = buffer.Bytes() |
| case Close: |
| logrus.Debugf("OpenFile: Close command") |
| if err := f.Close(); err != nil { |
| return err |
| } |
| default: |
| logrus.Errorf("OpenFile: unknown command") |
| return ErrUnknown |
| } |
| |
| logrus.Debugf("OpenFile: Writing back OK header of size %d", len(buf)) |
| retHdr := &FileHeader{ |
| Cmd: CmdOK, |
| Size: uint64(len(buf)), |
| } |
| if err := WriteFileHeader(out, retHdr, buf); err != nil { |
| logrus.Errorf("OpenFile: WriteFileHeader() failed: %v", err) |
| return err |
| } |
| |
| if hdr.Cmd == Close { |
| break |
| } |
| } |
| logrus.Debugf("OpenFile: Done, no error") |
| return nil |
| } |
| |
| // ReadFile works like ioutil.ReadFile but instead writes the file to a writer |
| // Args: |
| // - args[0] = path |
| // Out: |
| // - Write file contents to out |
| func ReadFile(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 1 { |
| return ErrInvalid |
| } |
| |
| f, err := os.Open(args[0]) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| |
| if _, err := io.Copy(out, f); err != nil { |
| return nil |
| } |
| return nil |
| } |
| |
| // WriteFile works like ioutil.WriteFile but instead reads the file from a reader |
| // Args: |
| // - args[0] = path |
| // - args[1] = permission mode in octal (like 0755) |
| // - input data stream from in |
| func WriteFile(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| |
| perm, err := strconv.ParseUint(args[1], 8, 32) |
| if err != nil { |
| return err |
| } |
| |
| f, err := os.OpenFile(args[0], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(perm)) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| |
| if _, err := io.Copy(f, in); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // ReadDir works like *os.File.Readdir but instead writes the result to a writer |
| // Args: |
| // - args[0] = path |
| // - args[1] = number of directory entries to return. If <= 0, return all entries in directory |
| func ReadDir(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| |
| n, err := strconv.ParseInt(args[1], 10, 32) |
| if err != nil { |
| return err |
| } |
| |
| f, err := os.Open(args[0]) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| |
| infos, err := f.Readdir(int(n)) |
| if err != nil { |
| return err |
| } |
| |
| fileInfos := make([]FileInfo, len(infos)) |
| for i := range infos { |
| fileInfos[i] = FileInfo{ |
| NameVar: infos[i].Name(), |
| SizeVar: infos[i].Size(), |
| ModeVar: infos[i].Mode(), |
| ModTimeVar: infos[i].ModTime().UnixNano(), |
| IsDirVar: infos[i].IsDir(), |
| } |
| } |
| |
| buf, err := json.Marshal(fileInfos) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := out.Write(buf); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // ResolvePath works like docker's symlink.FollowSymlinkInScope. |
| // It takens in a `path` and a `root` and evaluates symlinks in `path` |
| // as if they were scoped in `root`. `path` must be a child path of `root`. |
| // In other words, `path` must have `root` as a prefix. |
| // Example: |
| // path=/foo/bar -> /baz |
| // root=/foo, |
| // Expected result = /foo/baz |
| // |
| // Args: |
| // - args[0] is `path` |
| // - args[1] is `root` |
| // Out: |
| // - Write resolved path to stdout |
| func ResolvePath(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 2 { |
| return ErrInvalid |
| } |
| res, err := symlink.FollowSymlinkInScope(args[0], args[1]) |
| if err != nil { |
| return err |
| } |
| if _, err = out.Write([]byte(res)); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // ExtractArchive extracts the archive read from in. |
| // Args: |
| // - in = size of json | json of archive.TarOptions | input tar stream |
| // - args[0] = extract directory name |
| func ExtractArchive(in io.Reader, out io.Writer, args []string) error { |
| logrus.Debugln("ExtractArchive:", args) |
| if len(args) < 1 { |
| logrus.Errorln("ExtractArchive: invalid args") |
| return ErrInvalid |
| } |
| |
| opts, err := ReadTarOptions(in) |
| if err != nil { |
| logrus.Errorf("ExtractArchive: Failed to read tar options: %v", err) |
| return err |
| } |
| |
| logrus.Debugf("ExtractArchive: Tar options: %+v", opts) |
| if err := archive.Untar(in, args[0], opts); err != nil { |
| logrus.Errorf("ExtractArchive: Failed to Untar: %v", err) |
| return err |
| } |
| logrus.Debugf("ExtractArchive: Success") |
| return nil |
| } |
| |
| // ArchivePath archives the given directory and writes it to out. |
| // Args: |
| // - in = size of json | json of archive.TarOptions |
| // - args[0] = source directory name |
| // Out: |
| // - out = tar file of the archive |
| func ArchivePath(in io.Reader, out io.Writer, args []string) error { |
| if len(args) < 1 { |
| return ErrInvalid |
| } |
| |
| opts, err := ReadTarOptions(in) |
| if err != nil { |
| return err |
| } |
| |
| r, err := archive.TarWithOptions(args[0], opts) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := io.Copy(out, r); err != nil { |
| return err |
| } |
| return nil |
| } |