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