blob: ebc9e01df2fa6ffde144668ed93c9e81270e2eda [file] [log] [blame]
// +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
}