1// +build !windows
2
3package remotefs
4
5import (
6	"bytes"
7	"encoding/binary"
8	"encoding/json"
9	"io"
10	"os"
11	"path/filepath"
12	"strconv"
13
14	"github.com/docker/docker/pkg/archive"
15	"github.com/docker/docker/pkg/symlink"
16	"github.com/sirupsen/logrus"
17	"golang.org/x/sys/unix"
18)
19
20// Func is the function definition for a generic remote fs function
21// The input to the function is any serialized structs / data from in and the string slice
22// from args. The output of the function will be serialized and written to out.
23type Func func(stdin io.Reader, stdout io.Writer, args []string) error
24
25// Commands provide a string -> remotefs function mapping.
26// This is useful for commandline programs that will receive a string
27// as the function to execute.
28var Commands = map[string]Func{
29	StatCmd:           Stat,
30	LstatCmd:          Lstat,
31	ReadlinkCmd:       Readlink,
32	MkdirCmd:          Mkdir,
33	MkdirAllCmd:       MkdirAll,
34	RemoveCmd:         Remove,
35	RemoveAllCmd:      RemoveAll,
36	LinkCmd:           Link,
37	SymlinkCmd:        Symlink,
38	LchmodCmd:         Lchmod,
39	LchownCmd:         Lchown,
40	MknodCmd:          Mknod,
41	MkfifoCmd:         Mkfifo,
42	OpenFileCmd:       OpenFile,
43	ReadFileCmd:       ReadFile,
44	WriteFileCmd:      WriteFile,
45	ReadDirCmd:        ReadDir,
46	ResolvePathCmd:    ResolvePath,
47	ExtractArchiveCmd: ExtractArchive,
48	ArchivePathCmd:    ArchivePath,
49}
50
51// Stat functions like os.Stat.
52// Args:
53// - args[0] is the path
54// Out:
55// - out = FileInfo object
56func Stat(in io.Reader, out io.Writer, args []string) error {
57	return stat(in, out, args, os.Stat)
58}
59
60// Lstat functions like os.Lstat.
61// Args:
62// - args[0] is the path
63// Out:
64// - out = FileInfo object
65func Lstat(in io.Reader, out io.Writer, args []string) error {
66	return stat(in, out, args, os.Lstat)
67}
68
69func stat(in io.Reader, out io.Writer, args []string, statfunc func(string) (os.FileInfo, error)) error {
70	if len(args) < 1 {
71		return ErrInvalid
72	}
73
74	fi, err := statfunc(args[0])
75	if err != nil {
76		return err
77	}
78
79	info := FileInfo{
80		NameVar:    fi.Name(),
81		SizeVar:    fi.Size(),
82		ModeVar:    fi.Mode(),
83		ModTimeVar: fi.ModTime().UnixNano(),
84		IsDirVar:   fi.IsDir(),
85	}
86
87	buf, err := json.Marshal(info)
88	if err != nil {
89		return err
90	}
91
92	if _, err := out.Write(buf); err != nil {
93		return err
94	}
95	return nil
96}
97
98// Readlink works like os.Readlink
99// In:
100//  - args[0] is path
101// Out:
102//  - Write link result to out
103func Readlink(in io.Reader, out io.Writer, args []string) error {
104	if len(args) < 1 {
105		return ErrInvalid
106	}
107
108	l, err := os.Readlink(args[0])
109	if err != nil {
110		return err
111	}
112
113	if _, err := out.Write([]byte(l)); err != nil {
114		return err
115	}
116	return nil
117}
118
119// Mkdir works like os.Mkdir
120// Args:
121// - args[0] is the path
122// - args[1] is the permissions in octal (like 0755)
123func Mkdir(in io.Reader, out io.Writer, args []string) error {
124	return mkdir(in, out, args, os.Mkdir)
125}
126
127// MkdirAll works like os.MkdirAll.
128// Args:
129// - args[0] is the path
130// - args[1] is the permissions in octal (like 0755)
131func MkdirAll(in io.Reader, out io.Writer, args []string) error {
132	return mkdir(in, out, args, os.MkdirAll)
133}
134
135func mkdir(in io.Reader, out io.Writer, args []string, mkdirFunc func(string, os.FileMode) error) error {
136	if len(args) < 2 {
137		return ErrInvalid
138	}
139
140	perm, err := strconv.ParseUint(args[1], 8, 32)
141	if err != nil {
142		return err
143	}
144	return mkdirFunc(args[0], os.FileMode(perm))
145}
146
147// Remove works like os.Remove
148// Args:
149//	- args[0] is the path
150func Remove(in io.Reader, out io.Writer, args []string) error {
151	return remove(in, out, args, os.Remove)
152}
153
154// RemoveAll works like os.RemoveAll
155// Args:
156//  - args[0] is the path
157func RemoveAll(in io.Reader, out io.Writer, args []string) error {
158	return remove(in, out, args, os.RemoveAll)
159}
160
161func remove(in io.Reader, out io.Writer, args []string, removefunc func(string) error) error {
162	if len(args) < 1 {
163		return ErrInvalid
164	}
165	return removefunc(args[0])
166}
167
168// Link works like os.Link
169// Args:
170//  - args[0] = old path name (link source)
171//  - args[1] = new path name (link dest)
172func Link(in io.Reader, out io.Writer, args []string) error {
173	return link(in, out, args, os.Link)
174}
175
176// Symlink works like os.Symlink
177// Args:
178//  - args[0] = old path name (link source)
179//  - args[1] = new path name (link dest)
180func Symlink(in io.Reader, out io.Writer, args []string) error {
181	return link(in, out, args, os.Symlink)
182}
183
184func link(in io.Reader, out io.Writer, args []string, linkfunc func(string, string) error) error {
185	if len(args) < 2 {
186		return ErrInvalid
187	}
188	return linkfunc(args[0], args[1])
189}
190
191// Lchmod changes permission of the given file without following symlinks
192// Args:
193//  - args[0] = path
194//  - args[1] = permission mode in octal (like 0755)
195func Lchmod(in io.Reader, out io.Writer, args []string) error {
196	if len(args) < 2 {
197		return ErrInvalid
198	}
199
200	perm, err := strconv.ParseUint(args[1], 8, 32)
201	if err != nil {
202		return err
203	}
204
205	path := args[0]
206	if !filepath.IsAbs(path) {
207		path, err = filepath.Abs(path)
208		if err != nil {
209			return err
210		}
211	}
212	return unix.Fchmodat(0, path, uint32(perm), unix.AT_SYMLINK_NOFOLLOW)
213}
214
215// Lchown works like os.Lchown
216// Args:
217//  - args[0] = path
218//  - args[1] = uid in base 10
219//  - args[2] = gid in base 10
220func Lchown(in io.Reader, out io.Writer, args []string) error {
221	if len(args) < 3 {
222		return ErrInvalid
223	}
224
225	uid, err := strconv.ParseInt(args[1], 10, 64)
226	if err != nil {
227		return err
228	}
229
230	gid, err := strconv.ParseInt(args[2], 10, 64)
231	if err != nil {
232		return err
233	}
234	return os.Lchown(args[0], int(uid), int(gid))
235}
236
237// Mknod works like syscall.Mknod
238// Args:
239//  - args[0] = path
240//  - args[1] = permission mode in octal (like 0755)
241//  - args[2] = major device number in base 10
242//  - args[3] = minor device number in base 10
243func Mknod(in io.Reader, out io.Writer, args []string) error {
244	if len(args) < 4 {
245		return ErrInvalid
246	}
247
248	perm, err := strconv.ParseUint(args[1], 8, 32)
249	if err != nil {
250		return err
251	}
252
253	major, err := strconv.ParseInt(args[2], 10, 32)
254	if err != nil {
255		return err
256	}
257
258	minor, err := strconv.ParseInt(args[3], 10, 32)
259	if err != nil {
260		return err
261	}
262
263	dev := unix.Mkdev(uint32(major), uint32(minor))
264	return unix.Mknod(args[0], uint32(perm), int(dev))
265}
266
267// Mkfifo creates a FIFO special file with the given path name and permissions
268// Args:
269// 	- args[0] = path
270//  - args[1] = permission mode in octal (like 0755)
271func Mkfifo(in io.Reader, out io.Writer, args []string) error {
272	if len(args) < 2 {
273		return ErrInvalid
274	}
275
276	perm, err := strconv.ParseUint(args[1], 8, 32)
277	if err != nil {
278		return err
279	}
280	return unix.Mkfifo(args[0], uint32(perm))
281}
282
283// OpenFile works like os.OpenFile. To manage the file pointer state,
284// this function acts as a single file "file server" with Read/Write/Close
285// being serialized control codes from in.
286// Args:
287//  - args[0] = path
288//  - args[1] = flag in base 10
289//  - args[2] = permission mode in octal (like 0755)
290func OpenFile(in io.Reader, out io.Writer, args []string) (err error) {
291	logrus.Debugf("OpenFile: %v", args)
292
293	defer func() {
294		if err != nil {
295			logrus.Errorf("OpenFile: return is non-nil, so writing cmdFailed back: %v", err)
296			// error code will be serialized by the caller, so don't write it here
297			WriteFileHeader(out, &FileHeader{Cmd: CmdFailed}, nil)
298		}
299	}()
300
301	if len(args) < 3 {
302		logrus.Errorf("OpenFile: Not enough parameters")
303		return ErrInvalid
304	}
305
306	flag, err := strconv.ParseInt(args[1], 10, 32)
307	if err != nil {
308		logrus.Errorf("OpenFile: Invalid flag: %v", err)
309		return err
310	}
311
312	perm, err := strconv.ParseUint(args[2], 8, 32)
313	if err != nil {
314		logrus.Errorf("OpenFile: Invalid permission: %v", err)
315		return err
316	}
317
318	f, err := os.OpenFile(args[0], int(flag), os.FileMode(perm))
319	if err != nil {
320		logrus.Errorf("OpenFile: Failed to open: %v", err)
321		return err
322	}
323
324	// Signal the client that OpenFile succeeded
325	logrus.Debugf("OpenFile: Sending OK header")
326	if err := WriteFileHeader(out, &FileHeader{Cmd: CmdOK}, nil); err != nil {
327		return err
328	}
329
330	for {
331		logrus.Debugf("OpenFile: reading header")
332		hdr, err := ReadFileHeader(in)
333		if err != nil {
334			logrus.Errorf("OpenFile: Failed to ReadFileHeader: %v", err)
335			return err
336		}
337		logrus.Debugf("OpenFile: Header: %+v", hdr)
338
339		var buf []byte
340		switch hdr.Cmd {
341		case Read:
342			logrus.Debugf("OpenFile: Read command")
343			buf = make([]byte, hdr.Size, hdr.Size)
344			n, err := f.Read(buf)
345			logrus.Debugf("OpenFile: Issued a read for %d, got %d bytes and error %v", hdr.Size, n, err)
346			if err != nil {
347				logrus.Errorf("OpenFile: Read failed: %v", err)
348				return err
349			}
350			buf = buf[:n]
351		case Write:
352			logrus.Debugf("OpenFile: Write command")
353			if _, err := io.CopyN(f, in, int64(hdr.Size)); err != nil {
354				logrus.Errorf("OpenFile: Write CopyN() failed: %v", err)
355				return err
356			}
357		case Seek:
358			logrus.Debugf("OpenFile: Seek command")
359			seekHdr := &SeekHeader{}
360			if err := binary.Read(in, binary.BigEndian, seekHdr); err != nil {
361				logrus.Errorf("OpenFile: Seek Read() failed: %v", err)
362				return err
363			}
364			res, err := f.Seek(seekHdr.Offset, int(seekHdr.Whence))
365			if err != nil {
366				logrus.Errorf("OpenFile: Seek Seek() failed: %v", err)
367				return err
368			}
369			buffer := &bytes.Buffer{}
370			if err := binary.Write(buffer, binary.BigEndian, res); err != nil {
371				logrus.Errorf("OpenFile: Seek Write() failed: %v", err)
372				return err
373			}
374			buf = buffer.Bytes()
375		case Close:
376			logrus.Debugf("OpenFile: Close command")
377			if err := f.Close(); err != nil {
378				return err
379			}
380		default:
381			logrus.Errorf("OpenFile: unknown command")
382			return ErrUnknown
383		}
384
385		logrus.Debugf("OpenFile: Writing back OK header of size %d", len(buf))
386		retHdr := &FileHeader{
387			Cmd:  CmdOK,
388			Size: uint64(len(buf)),
389		}
390		if err := WriteFileHeader(out, retHdr, buf); err != nil {
391			logrus.Errorf("OpenFile: WriteFileHeader() failed: %v", err)
392			return err
393		}
394
395		if hdr.Cmd == Close {
396			break
397		}
398	}
399	logrus.Debugf("OpenFile: Done, no error")
400	return nil
401}
402
403// ReadFile works like ioutil.ReadFile but instead writes the file to a writer
404// Args:
405//  - args[0] = path
406// Out:
407//  - Write file contents to out
408func ReadFile(in io.Reader, out io.Writer, args []string) error {
409	if len(args) < 1 {
410		return ErrInvalid
411	}
412
413	f, err := os.Open(args[0])
414	if err != nil {
415		return err
416	}
417	defer f.Close()
418
419	if _, err := io.Copy(out, f); err != nil {
420		return nil
421	}
422	return nil
423}
424
425// WriteFile works like ioutil.WriteFile but instead reads the file from a reader
426// Args:
427//  - args[0] = path
428//  - args[1] = permission mode in octal (like 0755)
429//  - input data stream from in
430func WriteFile(in io.Reader, out io.Writer, args []string) error {
431	if len(args) < 2 {
432		return ErrInvalid
433	}
434
435	perm, err := strconv.ParseUint(args[1], 8, 32)
436	if err != nil {
437		return err
438	}
439
440	f, err := os.OpenFile(args[0], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(perm))
441	if err != nil {
442		return err
443	}
444	defer f.Close()
445
446	if _, err := io.Copy(f, in); err != nil {
447		return err
448	}
449	return nil
450}
451
452// ReadDir works like *os.File.Readdir but instead writes the result to a writer
453// Args:
454//  - args[0] = path
455//  - args[1] = number of directory entries to return. If <= 0, return all entries in directory
456func ReadDir(in io.Reader, out io.Writer, args []string) error {
457	if len(args) < 2 {
458		return ErrInvalid
459	}
460
461	n, err := strconv.ParseInt(args[1], 10, 32)
462	if err != nil {
463		return err
464	}
465
466	f, err := os.Open(args[0])
467	if err != nil {
468		return err
469	}
470	defer f.Close()
471
472	infos, err := f.Readdir(int(n))
473	if err != nil {
474		return err
475	}
476
477	fileInfos := make([]FileInfo, len(infos))
478	for i := range infos {
479		fileInfos[i] = FileInfo{
480			NameVar:    infos[i].Name(),
481			SizeVar:    infos[i].Size(),
482			ModeVar:    infos[i].Mode(),
483			ModTimeVar: infos[i].ModTime().UnixNano(),
484			IsDirVar:   infos[i].IsDir(),
485		}
486	}
487
488	buf, err := json.Marshal(fileInfos)
489	if err != nil {
490		return err
491	}
492
493	if _, err := out.Write(buf); err != nil {
494		return err
495	}
496	return nil
497}
498
499// ResolvePath works like docker's symlink.FollowSymlinkInScope.
500// It takens in a `path` and a `root` and evaluates symlinks in `path`
501// as if they were scoped in `root`. `path` must be a child path of `root`.
502// In other words, `path` must have `root` as a prefix.
503// Example:
504// path=/foo/bar -> /baz
505// root=/foo,
506// Expected result = /foo/baz
507//
508// Args:
509// - args[0] is `path`
510// - args[1] is `root`
511// Out:
512// - Write resolved path to stdout
513func ResolvePath(in io.Reader, out io.Writer, args []string) error {
514	if len(args) < 2 {
515		return ErrInvalid
516	}
517	res, err := symlink.FollowSymlinkInScope(args[0], args[1])
518	if err != nil {
519		return err
520	}
521	if _, err = out.Write([]byte(res)); err != nil {
522		return err
523	}
524	return nil
525}
526
527// ExtractArchive extracts the archive read from in.
528// Args:
529// - in = size of json | json of archive.TarOptions | input tar stream
530// - args[0] = extract directory name
531func ExtractArchive(in io.Reader, out io.Writer, args []string) error {
532	logrus.Debugln("ExtractArchive:", args)
533	if len(args) < 1 {
534		logrus.Errorln("ExtractArchive: invalid args")
535		return ErrInvalid
536	}
537
538	opts, err := ReadTarOptions(in)
539	if err != nil {
540		logrus.Errorf("ExtractArchive: Failed to read tar options: %v", err)
541		return err
542	}
543
544	logrus.Debugf("ExtractArchive: Tar options: %+v", opts)
545	if err := archive.Untar(in, args[0], opts); err != nil {
546		logrus.Errorf("ExtractArchive: Failed to Untar: %v", err)
547		return err
548	}
549	logrus.Debugf("ExtractArchive: Success")
550	return nil
551}
552
553// ArchivePath archives the given directory and writes it to out.
554// Args:
555// - in = size of json | json of archive.TarOptions
556// - args[0] = source directory name
557// Out:
558// - out = tar file of the archive
559func ArchivePath(in io.Reader, out io.Writer, args []string) error {
560	if len(args) < 1 {
561		return ErrInvalid
562	}
563
564	opts, err := ReadTarOptions(in)
565	if err != nil {
566		return err
567	}
568
569	r, err := archive.TarWithOptions(args[0], opts)
570	if err != nil {
571		return err
572	}
573
574	if _, err := io.Copy(out, r); err != nil {
575		return err
576	}
577	return nil
578}
579