v.io/jiri@v0.0.0-20160715023856-abfb8b131290/runutil/sequence.go (about) 1 // Copyright 2015 The Vanadium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package runutil 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "runtime" 16 "sync" 17 "syscall" 18 "time" 19 20 "v.io/x/lib/cmdline" 21 "v.io/x/lib/envvar" 22 ) 23 24 // Sequence provides for convenient chaining of multiple calls to its 25 // methods to avoid repeated tests for error returns. The usage is: 26 // 27 // err := s.Run("echo", "a").Run("echo", "b").Done() 28 // 29 // The first method to encounter an error short circuits any following 30 // methods and the result of that first error is returned by the 31 // Done method or any of the other 'terminating methods' (see below). 32 // Sequence is not thread safe. It also good practice to use a new 33 // instance of a Sequence in defer's. 34 // 35 // Unless directed to specific stdout and stderr io.Writers using Capture(), 36 // the stdout and stderr output from the command is discarded, unless an error 37 // is encountered, in which case the output from the command that failed (both 38 // stdout and stderr) is written to the stderr io.Writer specified via 39 // NewSequence. In addition, in verbose mode, command execution logging 40 // is written to the stdout and stderr io.Writers configured via NewSequence, 41 // and never to the stdout specified via Capture if the command succeeded. 42 // 43 // Modifier methods are provided that influence the behaviour of the 44 // next invocation of the Run method to set timeouts (Timed), to 45 // capture output (Capture), input (Read) and to set the environment (Env). 46 // For example, the following will result in a timeout error. 47 // 48 // err := s.Timeout(time.Second).Run("sleep","10").Done() 49 // err := s.Timeout(time.Second).Last("sleep","10") 50 // 51 // A sequence of commands must be terminated with a call to a 'terminating' 52 // method. The simplest are the Done or Last methods used in the examples above, 53 // but there are other methods which typically return results in addition to 54 // error, such as ReadFile(filename string) ([]byte, error). Here the usage 55 // would be: 56 // 57 // o.Stdout, _ = os.Create("foo") 58 // data, err := s.Capture(o, nil).Run("echo","b").ReadFile("foo") 59 // // data == "b" 60 // 61 // Note that terminating functions, even those that take an action, may 62 // return an error generated by a previous method. 63 // 64 // In addition to Run which will always run a command as a subprocess, 65 // the Call method will invoke a function. Note that Capture and Timeout 66 // do not affect such calls. 67 // 68 // Errors returned by Sequence augment those returned by the underlying 69 // packages with details of the exact call that generated those errors. 70 // This means that it is not possible to test directly for errors from 71 // those packages. The GetOriginalError function can be used to obtain 72 // the error from the underlying package, or the IsTimeout, IsNotExists etc 73 // functions can be used on the wrapped error. The ExitCode method 74 // is also provided to convert to the exit codes expected by the 75 // v.io/x/lib/cmdline package which is often used in conjunction with 76 // Sequence. 77 type Sequence struct { 78 // NOTE: we use a struct as the return value of all 79 // Sequence methods to ensure that code of the form: 80 // if err := s.WriteFile(); err != nil {...} 81 // does not compile. 82 *sequence 83 } 84 85 type sequence struct { 86 r *executor 87 err error 88 caller string 89 stdout, stderr io.Writer 90 stdin io.Reader 91 reading bool 92 env map[string]string 93 opts *opts 94 defaultStdin io.Reader 95 defaultStdout, defaultStderr io.Writer 96 dirs []string 97 verbosity *bool 98 cmdDir string 99 timeout time.Duration 100 serializedWriterLock sync.Mutex 101 } 102 103 // NewSequence creates an instance of Sequence with default values for its 104 // environment, stdin, stderr, stdout and other supported options. 105 // If the environment parameter is nil or empty then the current value of 106 // os.Environ() will be used instead. 107 func NewSequence(env map[string]string, stdin io.Reader, stdout, stderr io.Writer, color, verbose bool) Sequence { 108 if len(env) == 0 { 109 env = envvar.SliceToMap(os.Environ()) 110 } 111 s := Sequence{ 112 &sequence{ 113 r: newExecutor(env, stdin, stdout, stderr, color, verbose), 114 defaultStdin: stdin, 115 }, 116 } 117 s.defaultStdout, s.defaultStderr = s.serializeWriter(stdout), s.serializeWriter(stderr) 118 return s 119 } 120 121 // RunOpts returns the value of verbose that was used to 122 // create this sequence. 123 func (s Sequence) RunOpts() (verbose bool) { 124 opts := s.getOpts() 125 return opts.verbose 126 } 127 128 // Capture arranges for the next call to Run or Last to write its stdout and 129 // stderr output to the supplied io.Writers. This will be cleared and not used 130 // for any calls to Run or Last beyond the next one. Specifying nil for 131 // a writer will result in using the the corresponding io.Writer supplied 132 // to NewSequence. ioutil.Discard should be used to discard output. 133 func (s Sequence) Capture(stdout, stderr io.Writer) Sequence { 134 if s.err != nil { 135 return s 136 } 137 s.stdout, s.stderr = stdout, stderr 138 return s 139 } 140 141 // Read arranges for the next call to Run or Last to read from the supplied 142 // io.Reader. This will be cleared and not used for any calls to Run or Last 143 // beyond the next one. Specifying nil will result in reading from os.DevNull. 144 func (s Sequence) Read(stdin io.Reader) Sequence { 145 if s.err != nil { 146 return s 147 } 148 s.reading = true 149 s.stdin = stdin 150 return s 151 } 152 153 // SetEnv arranges for the next call to Run, Call, Start or Last to use the supplied 154 // environment variables. These will be cleared and not used for any calls 155 // to Run, Call or Last beyond the next one. 156 func (s Sequence) SetEnv(env map[string]string) Sequence { 157 if s.err != nil { 158 return s 159 } 160 s.env = env 161 return s 162 } 163 164 // Env arranges for the next call to Run, Call, Start or Last to use 165 // the result of merging the supplied environment variables with those 166 // specified when the sequence was created or with those set by the most 167 // recent call to the Env method. These will be cleared and not used 168 // for any calls to Run, Call or Last beyond the next one. 169 func (s Sequence) Env(env map[string]string) Sequence { 170 if s.err != nil { 171 return s 172 } 173 if s.env != nil { 174 s.env = envvar.MergeMaps(s.env, env) 175 } else { 176 e := s.getOpts().env 177 s.env = envvar.MergeMaps(e, env) 178 } 179 return s 180 } 181 182 // Verbosity arranges for the next call to Run, Call, Start or Last to use the 183 // specified verbosity. This will be cleared and not used for any calls 184 // to Run, Call or Last beyond the next one. 185 func (s Sequence) Verbose(verbosity bool) Sequence { 186 if s.err != nil { 187 return s 188 } 189 s.verbosity = &verbosity 190 return s 191 } 192 193 // Dir sets the working directory for the next subprocess that is created 194 // via Run, Call, Start or Last to the supplied parameter. This is the only 195 // way to safely set the working directory of a command when multiple threads 196 // are used. 197 func (s Sequence) Dir(dir string) Sequence { 198 if s.err != nil { 199 return s 200 } 201 s.cmdDir = dir 202 return s 203 } 204 205 // internal getOpts that doesn't override stdin, stdout, stderr 206 func (s Sequence) getOpts() opts { 207 var opts opts 208 if s.opts != nil { 209 opts = *s.opts 210 } else { 211 opts = s.r.opts 212 } 213 return opts 214 } 215 216 // Timeout arranges for the next call to Run, Start or Last to be subject to the 217 // specified timeout. The timeout will be cleared and not used any calls to Run 218 // or Last beyond the next one. It has no effect for calls to Call. 219 func (s Sequence) Timeout(timeout time.Duration) Sequence { 220 if s.err != nil { 221 return s 222 } 223 s.timeout = timeout 224 return s 225 } 226 227 func (s Sequence) setOpts(opts opts) { 228 s.opts = &opts 229 } 230 231 type wrappedError struct { 232 oe, we error 233 } 234 235 func (ie *wrappedError) Error() string { 236 return ie.we.Error() 237 } 238 239 // Error returns the error, if any, stored in the Sequence. 240 func (s Sequence) Error() error { 241 if s.err != nil && len(s.caller) > 0 { 242 return &wrappedError{oe: s.err, we: fmt.Errorf("%s: %v", s.caller, s.err)} 243 } 244 return s.err 245 } 246 247 // TranslateExitCode translates errors from the "os/exec" package that 248 // contain exit codes into cmdline.ErrExitCode errors. 249 func TranslateExitCode(err error) error { 250 return translateExitCode(GetOriginalError(err)) 251 } 252 253 func translateExitCode(err error) error { 254 if exit, ok := err.(*exec.ExitError); ok { 255 if wait, ok := exit.Sys().(syscall.WaitStatus); ok { 256 if status := wait.ExitStatus(); wait.Exited() && status != 0 { 257 return cmdline.ErrExitCode(status) 258 } 259 } 260 } 261 return err 262 } 263 264 // GetOriginalError gets the original error wrapped in the supplied err. 265 // If the given err has not been wrapped by Sequence, then the supplied error 266 // is returned. 267 func GetOriginalError(err error) error { 268 if we, ok := err.(*wrappedError); ok { 269 return we.oe 270 } 271 return err 272 } 273 274 // IsExist returns a boolean indicating whether the error is known 275 // to report that a file or directory already exists. 276 func IsExist(err error) bool { 277 if we, ok := err.(*wrappedError); ok { 278 return os.IsExist(we.oe) 279 } 280 return os.IsExist(err) 281 } 282 283 // IsNotExist returns a boolean indicating whether the error is known 284 // to report that a file or directory does not exist. 285 func IsNotExist(err error) bool { 286 if we, ok := err.(*wrappedError); ok { 287 return os.IsNotExist(we.oe) 288 } 289 return os.IsNotExist(err) 290 } 291 292 // IsPermission returns a boolean indicating whether the error is known 293 // to report that permission is denied. 294 func IsPermission(err error) bool { 295 if we, ok := err.(*wrappedError); ok { 296 return os.IsPermission(we.oe) 297 } 298 return os.IsPermission(err) 299 } 300 301 // IsTimeout returns a boolean indicating whether the error is a result of 302 // a timeout. 303 func IsTimeout(err error) bool { 304 if we, ok := err.(*wrappedError); ok { 305 return we.oe == commandTimedOutErr 306 } 307 return err == commandTimedOutErr 308 } 309 310 func fmtError(depth int, err error, detail string) string { 311 _, file, line, _ := runtime.Caller(depth + 1) 312 return fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, detail) 313 } 314 315 func (s Sequence) setError(err error, detail string) { 316 if err == nil || s.err != nil { 317 return 318 } 319 s.err = err 320 s.caller = fmtError(2, err, detail) 321 } 322 323 // reset all state except s.err 324 func (s Sequence) reset() { 325 s.stdin, s.stdout, s.stderr, s.env = nil, nil, nil, nil 326 s.opts, s.verbosity = nil, nil 327 s.cmdDir = "" 328 s.reading = false 329 s.timeout = 0 330 } 331 332 func cleanup(p1, p2 *io.PipeWriter, stdinCh, stderrCh chan error) error { 333 p1.Close() 334 p2.Close() 335 if stdinCh != nil { 336 if err := <-stdinCh; err != nil { 337 return err 338 } 339 } 340 if stderrCh != nil { 341 if err := <-stderrCh; err != nil { 342 return err 343 } 344 } 345 return nil 346 } 347 348 func useIfNotNil(a, b io.Writer) io.Writer { 349 if a != nil { 350 return a 351 } 352 return b 353 } 354 355 func writeOutput(logdir bool, from string, to io.Writer) { 356 if fi, err := os.Open(from); err == nil { 357 io.Copy(to, fi) 358 fi.Close() 359 } 360 if !logdir { 361 return 362 } 363 if wd, err := os.Getwd(); err == nil { 364 fmt.Fprintf(to, "Current Directory: %v\n", wd) 365 } 366 } 367 368 type sharedLockWriter struct { 369 mu *sync.Mutex 370 f io.Writer 371 } 372 373 func (lw *sharedLockWriter) Write(d []byte) (int, error) { 374 lw.mu.Lock() 375 defer lw.mu.Unlock() 376 return lw.f.Write(d) 377 } 378 379 func (s Sequence) serializeWriter(a io.Writer) io.Writer { 380 if a != nil { 381 return &sharedLockWriter{&s.serializedWriterLock, a} 382 } 383 return nil 384 } 385 386 // isWriterTTY returns whether the io.Writer w is an os.File pointing at a tty. 387 func isWriterTTY(w io.Writer) (isTTY bool) { 388 _, isOsFile := w.(*os.File) 389 if isOsFile { 390 stat, _ := os.Stdin.Stat() 391 isTTY = (stat.Mode() & (os.ModeDevice | os.ModeCharDevice)) == (os.ModeDevice | os.ModeCharDevice) 392 } 393 return isTTY 394 } 395 396 func (s Sequence) initAndDefer(h *Handle) func() { 397 if s.stdout == nil && s.stderr == nil { 398 fout, err := ioutil.TempFile("", "seq") 399 if err != nil { 400 return func() {} 401 } 402 opts := s.getOpts() 403 opts.stdout, opts.stderr = s.serializeWriter(fout), s.serializeWriter(fout) 404 if len(s.env) > 0 { 405 opts.env = s.env 406 } 407 if s.reading { 408 opts.stdin = s.stdin 409 } 410 if s.verbosity != nil { 411 opts.verbose = *s.verbosity 412 } 413 opts.dir = s.cmdDir 414 s.setOpts(opts) 415 if h != nil { 416 return func() { 417 h.stderr = useIfNotNil(s.defaultStderr, os.Stderr) 418 h.filename = fout.Name() 419 h.doneErr = nil 420 fout.Close() 421 } 422 } 423 return func() { 424 filename := fout.Name() 425 fout.Close() 426 defer func() { os.Remove(filename); s.opts = nil }() 427 if s.err != nil { 428 writeOutput(true, filename, useIfNotNil(s.defaultStderr, os.Stderr)) 429 } 430 if opts.verbose && s.defaultStderr != s.defaultStdout { 431 writeOutput(false, filename, useIfNotNil(s.defaultStdout, os.Stdout)) 432 } 433 } 434 } 435 opts := s.getOpts() 436 rStdout, wStdout := io.Pipe() 437 rStderr, wStderr := io.Pipe() 438 // If the requested stdout/stderr is a raw os.File pointing at a tty, 439 // use that directly. This allows the invocation of tools 440 // like editors, which expect stdout/stderr to be ttys. 441 if isWriterTTY(s.stdout) { 442 opts.stdout = s.stdout 443 } else { 444 opts.stdout = wStdout 445 } 446 if isWriterTTY(s.stderr) { 447 opts.stderr = s.stderr 448 } else { 449 opts.stderr = wStderr 450 } 451 if len(s.env) > 0 { 452 opts.env = s.env 453 } 454 if s.reading { 455 opts.stdin = s.stdin 456 } 457 var stdinCh, stderrCh chan error 458 stdout, stderr := s.serializeWriter(s.stdout), s.serializeWriter(s.stderr) 459 if stdout != nil { 460 stdinCh = make(chan error) 461 go copy(stdout, rStdout, stdinCh) 462 } else { 463 opts.stdout = s.defaultStdout 464 } 465 if stderr != nil { 466 stderrCh = make(chan error) 467 go copy(stderr, rStderr, stderrCh) 468 } else { 469 opts.stderr = s.defaultStderr 470 } 471 if s.verbosity != nil { 472 opts.verbose = *s.verbosity 473 } 474 opts.dir = s.cmdDir 475 s.setOpts(opts) 476 if h != nil { 477 return func() { 478 h.filename = "" 479 h.doneErr = cleanup(wStdout, wStderr, stdinCh, stderrCh) 480 } 481 } 482 return func() { 483 if err := cleanup(wStdout, wStderr, stdinCh, stderrCh); err != nil && s.err == nil { 484 // If we haven't already encountered an error and we fail to 485 // cleanup then record the error from the cleanup 486 s.err = err 487 } 488 // reset does not affect s.err 489 s.reset() 490 } 491 } 492 493 func fmtStringArgs(args ...string) string { 494 if len(args) == 0 { 495 return "" 496 } 497 var out bytes.Buffer 498 for _, a := range args { 499 fmt.Fprintf(&out, ", %q", a) 500 } 501 return out.String() 502 } 503 504 // Run runs the given command as a subprocess. 505 func (s Sequence) Run(path string, args ...string) Sequence { 506 if s.err != nil { 507 return s 508 } 509 defer s.initAndDefer(nil)() 510 s.setError(s.r.run(s.timeout, s.getOpts(), path, args...), fmt.Sprintf("Run(%q%s)", path, fmtStringArgs(args...))) 511 return s 512 } 513 514 // Last runs the given command as a subprocess and returns an error 515 // immediately terminating the sequence, it is equivalent to 516 // calling s.Run(path, args...).Done(). 517 func (s Sequence) Last(path string, args ...string) error { 518 if s.err != nil { 519 return s.Done() 520 } 521 defer s.Done() 522 defer s.initAndDefer(nil)() 523 s.setError(s.r.run(s.timeout, s.getOpts(), path, args...), fmt.Sprintf("Last(%q%s)", path, fmtStringArgs(args...))) 524 return s.Error() 525 } 526 527 // Call runs the given function. Note that Capture and Timeout have no 528 // effect on invocations of Call, but Opts can control logging. 529 func (s Sequence) Call(fn func() error, format string, args ...interface{}) Sequence { 530 if s.err != nil { 531 return s 532 } 533 defer s.initAndDefer(nil)() 534 s.setError(s.r.function(s.getOpts(), fn, format, args...), fmt.Sprintf(format, args...)) 535 return s 536 } 537 538 // Handle represents a command running in the background. 539 type Handle struct { 540 stdout, stderr io.Writer 541 doneErr error 542 filename string 543 deferFn func() 544 cmd *exec.Cmd 545 } 546 547 // Kill terminates the currently running background process. 548 func (h *Handle) Kill() error { 549 return h.cmd.Process.Kill() 550 } 551 552 // Pid returns the pid of the running process. 553 func (h *Handle) Pid() int { 554 return h.cmd.Process.Pid 555 } 556 557 func (h *Handle) Signal(sig os.Signal) error { 558 return h.cmd.Process.Signal(sig) 559 } 560 561 // Wait waits for the currently running background process to terminate. 562 func (h *Handle) Wait() error { 563 err := h.cmd.Wait() 564 h.deferFn() 565 if len(h.filename) > 0 { 566 if err != nil { 567 writeOutput(true, h.filename, h.stderr) 568 } 569 os.Remove(h.filename) 570 return err 571 } 572 return h.doneErr 573 } 574 575 // Start runs the given command as a subprocess in background and returns 576 // a handle that can be used to kill and/or wait for that background process. 577 // Start is a terminating function. 578 func (s Sequence) Start(path string, args ...string) (*Handle, error) { 579 if s.err != nil { 580 return nil, s.Done() 581 } 582 h := &Handle{} 583 h.deferFn = s.initAndDefer(h) 584 cmd, err := s.r.start(s.timeout, s.getOpts(), path, args...) 585 h.cmd = cmd 586 s.setError(err, fmt.Sprintf("Start(%q%s)", path, fmtStringArgs(args...))) 587 return h, s.Error() 588 } 589 590 // Output logs the given list of lines using the currently in effect verbosity 591 // as specified by Opts, or the default otherwise. 592 func (s Sequence) Output(output []string) Sequence { 593 if s.err != nil { 594 return s 595 } 596 opts := s.getOpts() 597 if s.verbosity != nil { 598 opts.verbose = *s.verbosity 599 } 600 s.r.output(opts, output) 601 return s 602 } 603 604 // Fprintf calls fmt.Fprintf. 605 func (s Sequence) Fprintf(f io.Writer, format string, args ...interface{}) Sequence { 606 if s.err != nil { 607 return s 608 } 609 fmt.Fprintf(f, format, args...) 610 return s 611 } 612 613 // Done returns the error stored in the Sequence and pops back to the first 614 // entry in the directory stack if Pushd has been called. Done is a terminating 615 // function. There is no need to ensure that Done is called before returning 616 // from a function that uses a sequence unless it is necessary to pop the 617 // stack. 618 func (s Sequence) Done() error { 619 rerr := s.Error() 620 s.err = nil 621 s.caller = "" 622 s.reset() 623 if len(s.dirs) > 0 { 624 cwd := s.dirs[0] 625 s.dirs = nil 626 err := s.r.call(func() error { 627 return os.Chdir(cwd) 628 }, fmt.Sprintf("sequence done popd %q", cwd)) 629 if err != nil { 630 detail := "Done: Chdir(" + cwd + ")" 631 if rerr == nil { 632 s.setError(err, detail) 633 } else { 634 // In the unlikely event that Chdir fails in addition to an 635 // earlier error, we append an appropriate error message. 636 s.err = fmt.Errorf("%v\n%v", rerr, fmtError(1, err, detail)) 637 } 638 return s.Error() 639 } 640 } 641 return rerr 642 } 643 644 // Pushd pushes the current directory onto a stack and changes directory 645 // to the specified one. Calling any terminating function will pop back 646 // to the first element in the stack on completion of that function. 647 func (s Sequence) Pushd(dir string) Sequence { 648 cwd, err := os.Getwd() 649 if err != nil { 650 s.setError(err, "Pushd("+dir+"): os.Getwd") 651 return s 652 } 653 s.dirs = append(s.dirs, cwd) 654 err = s.r.call(func() error { 655 return os.Chdir(dir) 656 }, fmt.Sprintf("pushd %q", dir)) 657 s.setError(err, "Pushd("+dir+")") 658 return s 659 } 660 661 // Popd popds the last directory from the directory stack and chdir's to it. 662 // Calling any termination function will pop back to the first element in 663 // the stack on completion of that function. 664 func (s Sequence) Popd() Sequence { 665 if s.err != nil { 666 return s 667 } 668 if len(s.dirs) == 0 { 669 s.setError(fmt.Errorf("directory stack is empty"), "Popd()") 670 return s 671 } 672 last := s.dirs[len(s.dirs)-1] 673 s.dirs = s.dirs[:len(s.dirs)-1] 674 err := s.r.call(func() error { 675 return os.Chdir(last) 676 }, fmt.Sprintf("popd %q", last)) 677 s.setError(err, "Popd() -> "+last) 678 return s 679 } 680 681 // Chdir is a wrapper around os.Chdir that handles options such as 682 // "verbose". 683 func (s Sequence) Chdir(dir string) Sequence { 684 if s.err != nil { 685 return s 686 } 687 err := s.r.call(func() error { 688 return os.Chdir(dir) 689 }, fmt.Sprintf("cd %q", dir)) 690 s.setError(err, "Chdir("+dir+")") 691 return s 692 693 } 694 695 // Chmod is a wrapper around os.Chmod that handles options such as 696 // "verbose". 697 func (s Sequence) Chmod(dir string, mode os.FileMode) Sequence { 698 if s.err != nil { 699 return s 700 } 701 err := s.r.call(func() error { return os.Chmod(dir, mode) }, fmt.Sprintf("chmod %v %q", mode, dir)) 702 s.setError(err, fmt.Sprintf("Chmod(%s, %s)", dir, mode)) 703 return s 704 705 } 706 707 // MkdirAll is a wrapper around os.MkdirAll that handles options such 708 // as "verbose". 709 func (s Sequence) MkdirAll(dir string, mode os.FileMode) Sequence { 710 if s.err != nil { 711 return s 712 } 713 err := s.r.call(func() error { return os.MkdirAll(dir, mode) }, fmt.Sprintf("mkdir -p %q", dir)) 714 s.setError(err, fmt.Sprintf("MkdirAll(%s, %s)", dir, mode)) 715 return s 716 } 717 718 // RemoveAll is a wrapper around os.RemoveAll that handles options 719 // such as "verbose". 720 func (s Sequence) RemoveAll(dir string) Sequence { 721 if s.err != nil { 722 return s 723 } 724 err := s.r.call(func() error { return os.RemoveAll(dir) }, fmt.Sprintf("rm -rf %q", dir)) 725 s.setError(err, fmt.Sprintf("RemoveAll(%s)", dir)) 726 return s 727 } 728 729 // Remove is a wrapper around os.Remove that handles options 730 // such as "verbose". 731 func (s Sequence) Remove(file string) Sequence { 732 if s.err != nil { 733 return s 734 } 735 err := s.r.call(func() error { return os.Remove(file) }, fmt.Sprintf("rm %q", file)) 736 s.setError(err, fmt.Sprintf("Remove(%s)", file)) 737 return s 738 } 739 740 // Rename is a wrapper around os.Rename that handles options such as 741 // "verbose". 742 func (s Sequence) Rename(src, dst string) Sequence { 743 if s.err != nil { 744 return s 745 } 746 err := s.r.call(func() error { 747 if err := os.Rename(src, dst); err != nil { 748 // Check if the rename operation failed 749 // because the source and destination are 750 // located on different mount points. 751 linkErr, ok := err.(*os.LinkError) 752 if !ok { 753 return err 754 } 755 errno, ok := linkErr.Err.(syscall.Errno) 756 if !ok || errno != syscall.EXDEV { 757 return err 758 } 759 // Fall back to a non-atomic rename. 760 cmd := exec.Command("mv", src, dst) 761 return cmd.Run() 762 } 763 return nil 764 }, fmt.Sprintf("mv %q %q", src, dst)) 765 s.setError(err, fmt.Sprintf("Rename(%s, %s)", src, dst)) 766 return s 767 768 } 769 770 // Symlink is a wrapper around os.Symlink that handles options such as 771 // "verbose". 772 func (s Sequence) Symlink(src, dst string) Sequence { 773 if s.err != nil { 774 return s 775 } 776 err := s.r.call(func() error { return os.Symlink(src, dst) }, fmt.Sprintf("ln -s %q %q", src, dst)) 777 s.setError(err, fmt.Sprintf("Symlink(%s, %s)", src, dst)) 778 return s 779 } 780 781 // Open is a wrapper around os.Open that handles options such as 782 // "verbose". Open is a terminating function. 783 func (s Sequence) Open(name string) (f *os.File, err error) { 784 if s.err != nil { 785 return nil, s.Done() 786 } 787 s.r.call(func() error { 788 f, err = os.Open(name) 789 return err 790 }, fmt.Sprintf("open %q", name)) 791 s.setError(err, fmt.Sprintf("Open(%s)", name)) 792 err = s.Done() 793 return 794 } 795 796 // OpenFile is a wrapper around os.OpenFile that handles options such as 797 // "verbose". OpenFile is a terminating function. 798 func (s Sequence) OpenFile(name string, flag int, perm os.FileMode) (f *os.File, err error) { 799 if s.err != nil { 800 return nil, s.Done() 801 } 802 s.r.call(func() error { 803 f, err = os.OpenFile(name, flag, perm) 804 return err 805 }, fmt.Sprintf("open file %q", name)) 806 s.setError(err, fmt.Sprintf("OpenFile(%s, 0x%x, %s)", name, flag, perm)) 807 err = s.Done() 808 return 809 } 810 811 // Create is a wrapper around os.Create that handles options such as "verbose" 812 //. Create is a terminating function. 813 func (s Sequence) Create(name string) (f *os.File, err error) { 814 if s.err != nil { 815 return nil, s.Done() 816 } 817 s.r.call(func() error { 818 f, err = os.Create(name) 819 return err 820 }, fmt.Sprintf("create %q", name)) 821 s.setError(err, fmt.Sprintf("Create(%s)", name)) 822 err = s.Done() 823 return 824 } 825 826 // ReadDir is a wrapper around ioutil.ReadDir that handles options 827 // such as "verbose". ReadDir is a terminating function. 828 func (s Sequence) ReadDir(dirname string) (fi []os.FileInfo, err error) { 829 if s.err != nil { 830 return nil, s.Done() 831 } 832 s.r.call(func() error { 833 fi, err = ioutil.ReadDir(dirname) 834 return err 835 }, fmt.Sprintf("ls %q", dirname)) 836 s.setError(err, fmt.Sprintf("ReadDir(%s)", dirname)) 837 err = s.Done() 838 return 839 } 840 841 // ReadFile is a wrapper around ioutil.ReadFile that handles options 842 // such as "verbose". ReadFile is a terminating function. 843 func (s Sequence) ReadFile(filename string) (bytes []byte, err error) { 844 845 if s.err != nil { 846 return nil, s.Done() 847 } 848 s.r.call(func() error { 849 bytes, err = ioutil.ReadFile(filename) 850 return err 851 }, fmt.Sprintf("read %q", filename)) 852 s.setError(err, fmt.Sprintf("ReadFile(%s)", filename)) 853 err = s.Done() 854 return 855 } 856 857 // WriteFile is a wrapper around ioutil.WriteFile that handles options 858 // such as "verbose". 859 func (s Sequence) WriteFile(filename string, data []byte, perm os.FileMode) Sequence { 860 if s.err != nil { 861 return s 862 } 863 err := s.r.call(func() error { 864 return ioutil.WriteFile(filename, data, perm) 865 }, fmt.Sprintf("write %q", filename)) 866 s.setError(err, fmt.Sprintf("WriteFile(%s, %.10s, %s)", filename, data, perm)) 867 return s 868 } 869 870 // Copy is a wrapper around io.Copy that handles options such as "verbose". 871 // Copy is a terminating function. 872 func (s Sequence) Copy(dst io.Writer, src io.Reader) (n int64, err error) { 873 if s.err != nil { 874 return 0, s.Done() 875 } 876 s.r.call(func() error { 877 n, err = io.Copy(dst, src) 878 return err 879 }, "io.copy") 880 s.setError(err, fmt.Sprintf("Copy(%s, %s)", dst, src)) 881 err = s.Done() 882 return 883 } 884 885 // Stat is a wrapper around os.Stat that handles options such as 886 // "verbose". Stat is a terminating function. 887 func (s Sequence) Stat(name string) (fi os.FileInfo, err error) { 888 if s.err != nil { 889 return nil, s.Done() 890 } 891 s.r.call(func() error { 892 fi, err = os.Stat(name) 893 return err 894 }, fmt.Sprintf("stat %q", name)) 895 s.setError(err, fmt.Sprintf("Stat(%s)", name)) 896 err = s.Done() 897 return 898 } 899 900 // Lstat is a wrapper around os.Lstat that handles options such as 901 // "verbose". Lstat is a terminating function. 902 func (s Sequence) Lstat(name string) (fi os.FileInfo, err error) { 903 if s.err != nil { 904 return nil, s.Done() 905 } 906 s.r.call(func() error { 907 fi, err = os.Lstat(name) 908 return err 909 }, fmt.Sprintf("lstat %q", name)) 910 s.setError(err, fmt.Sprintf("Lstat(%s)", name)) 911 err = s.Done() 912 return 913 } 914 915 // Readlink is a wrapper around os.Readlink that handles options such as 916 // "verbose". Lstat is a terminating function. 917 func (s Sequence) Readlink(name string) (link string, err error) { 918 if s.err != nil { 919 return "", s.Done() 920 } 921 s.r.call(func() error { 922 link, err = os.Readlink(name) 923 return err 924 }, fmt.Sprintf("readlink %q", name)) 925 s.setError(err, fmt.Sprintf("Readlink(%s)", name)) 926 err = s.Done() 927 return 928 } 929 930 // TempDir is a wrapper around ioutil.TempDir that handles options 931 // such as "verbose". TempDir is a terminating function. 932 func (s Sequence) TempDir(dir, prefix string) (tmpDir string, err error) { 933 if s.err != nil { 934 return "", s.Done() 935 } 936 if dir == "" { 937 dir = os.Getenv("TMPDIR") 938 } 939 tmpDir = filepath.Join(dir, prefix+"XXXXXX") 940 s.r.call(func() error { 941 tmpDir, err = ioutil.TempDir(dir, prefix) 942 return err 943 }, fmt.Sprintf("mkdir -p %q", tmpDir)) 944 s.setError(err, fmt.Sprintf("TempDir(%s,%s)", dir, prefix)) 945 err = s.Done() 946 return 947 } 948 949 // TempFile is a wrapper around ioutil.TempFile that handles options 950 // such as "verbose". 951 func (s Sequence) TempFile(dir, prefix string) (f *os.File, err error) { 952 if s.err != nil { 953 return nil, s.Done() 954 } 955 if dir == "" { 956 dir = os.Getenv("TMPDIR") 957 } 958 s.r.call(func() error { 959 f, err = ioutil.TempFile(dir, prefix) 960 return err 961 }, fmt.Sprintf("tempFile %q %q", dir, prefix)) 962 s.setError(err, fmt.Sprintf("TempFile(%s,%s)", dir, prefix)) 963 err = s.Done() 964 return 965 } 966 967 // IsDir is a wrapper around os.Stat with appropriate logging that 968 // returns true of dirname exists and is a directory. 969 // IsDir is a terminating function. 970 func (s Sequence) IsDir(dirname string) (bool, error) { 971 if s.err != nil { 972 return false, s.Done() 973 } 974 var fileInfo os.FileInfo 975 var err error 976 err = s.r.call(func() error { 977 fileInfo, err = os.Stat(dirname) 978 return err 979 }, fmt.Sprintf("isdir %q", dirname)) 980 if IsNotExist(err) { 981 return false, nil 982 } 983 if err != nil { 984 return false, err 985 } 986 s.setError(err, fmt.Sprintf("IsDir(%s)", dirname)) 987 return fileInfo.IsDir(), s.Done() 988 } 989 990 // IsFile is a wrapper around os.Stat with appropriate logging that 991 // returns true if file exists and is not a directory. 992 // IsFile is a terminating function. 993 func (s Sequence) IsFile(file string) (bool, error) { 994 if s.err != nil { 995 return false, s.Done() 996 } 997 var fileInfo os.FileInfo 998 var err error 999 err = s.r.call(func() error { 1000 fileInfo, err = os.Stat(file) 1001 return err 1002 }, fmt.Sprintf("isfile %q", file)) 1003 if IsNotExist(err) { 1004 return false, nil 1005 } 1006 if err != nil { 1007 return false, err 1008 } 1009 s.setError(err, fmt.Sprintf("IsFile(%s)", file)) 1010 return !fileInfo.IsDir(), s.Done() 1011 } 1012 1013 // AssertDirExists asserts that the specified directory exists with appropriate 1014 // logging. 1015 func (s Sequence) AssertDirExists(dirname string) Sequence { 1016 if s.err != nil { 1017 return s 1018 } 1019 isdir, err := s.IsDir(dirname) 1020 if !isdir && err == nil { 1021 err = os.ErrNotExist 1022 } 1023 s.setError(err, fmt.Sprintf("AssertDirExists(%s)", dirname)) 1024 return s 1025 } 1026 1027 // AssertFileExists asserts that the specified file exists with appropriate 1028 // logging. 1029 func (s Sequence) AssertFileExists(file string) Sequence { 1030 if s.err != nil { 1031 return s 1032 } 1033 isfile, err := s.IsFile(file) 1034 if !isfile && err == nil { 1035 err = os.ErrNotExist 1036 } 1037 s.setError(err, fmt.Sprintf("AssertFileExists(%s)", file)) 1038 return s 1039 } 1040 1041 func copy(to io.Writer, from io.Reader, ch chan error) { 1042 _, err := io.Copy(to, from) 1043 ch <- err 1044 }