github.com/rigado/snapd@v2.42.5-go-mod+incompatible/osutil/cp.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package osutil
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  )
    29  
    30  // CopyFlag is used to tweak the behaviour of CopyFile
    31  type CopyFlag uint8
    32  
    33  const (
    34  	// CopyFlagDefault is the default behaviour
    35  	CopyFlagDefault CopyFlag = 0
    36  	// CopyFlagSync does a sync after copying the files
    37  	CopyFlagSync CopyFlag = 1 << iota
    38  	// CopyFlagOverwrite overwrites the target if it exists
    39  	CopyFlagOverwrite
    40  	// CopyFlagPreserveAll preserves mode,owner,time attributes
    41  	CopyFlagPreserveAll
    42  )
    43  
    44  var (
    45  	openfile = doOpenFile
    46  	copyfile = doCopyFile
    47  )
    48  
    49  type fileish interface {
    50  	Close() error
    51  	Sync() error
    52  	Fd() uintptr
    53  	Stat() (os.FileInfo, error)
    54  	Read([]byte) (int, error)
    55  	Write([]byte) (int, error)
    56  }
    57  
    58  func doOpenFile(name string, flag int, perm os.FileMode) (fileish, error) {
    59  	return os.OpenFile(name, flag, perm)
    60  }
    61  
    62  // CopyFile copies src to dst
    63  func CopyFile(src, dst string, flags CopyFlag) (err error) {
    64  	if flags&CopyFlagPreserveAll != 0 {
    65  		// Our native copy code does not preserve all attributes
    66  		// (yet). If the user needs this functionatlity we just
    67  		// fallback to use the system's "cp" binary to do the copy.
    68  		if err := runCpPreserveAll(src, dst, "copy all"); err != nil {
    69  			return err
    70  		}
    71  		if flags&CopyFlagSync != 0 {
    72  			return runSync()
    73  		}
    74  		return nil
    75  	}
    76  
    77  	fin, err := openfile(src, os.O_RDONLY, 0)
    78  	if err != nil {
    79  		return fmt.Errorf("unable to open %s: %v", src, err)
    80  	}
    81  	defer func() {
    82  		if cerr := fin.Close(); cerr != nil && err == nil {
    83  			err = fmt.Errorf("when closing %s: %v", src, cerr)
    84  		}
    85  	}()
    86  
    87  	fi, err := fin.Stat()
    88  	if err != nil {
    89  		return fmt.Errorf("unable to stat %s: %v", src, err)
    90  	}
    91  
    92  	outflags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
    93  	if flags&CopyFlagOverwrite == 0 {
    94  		outflags |= os.O_EXCL
    95  	}
    96  
    97  	fout, err := openfile(dst, outflags, fi.Mode())
    98  	if err != nil {
    99  		return fmt.Errorf("unable to create %s: %v", dst, err)
   100  	}
   101  	defer func() {
   102  		if cerr := fout.Close(); cerr != nil && err == nil {
   103  			err = fmt.Errorf("when closing %s: %v", dst, cerr)
   104  		}
   105  	}()
   106  
   107  	if err := copyfile(fin, fout, fi); err != nil {
   108  		return fmt.Errorf("unable to copy %s to %s: %v", src, dst, err)
   109  	}
   110  
   111  	if flags&CopyFlagSync != 0 {
   112  		if err = fout.Sync(); err != nil {
   113  			return fmt.Errorf("unable to sync %s: %v", dst, err)
   114  		}
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func runCmd(cmd *exec.Cmd, errdesc string) error {
   121  	if output, err := cmd.CombinedOutput(); err != nil {
   122  		output = bytes.TrimSpace(output)
   123  		if exitCode, err := ExitCode(err); err == nil {
   124  			return &CopySpecialFileError{
   125  				desc:     errdesc,
   126  				exitCode: exitCode,
   127  				output:   output,
   128  			}
   129  		}
   130  		return &CopySpecialFileError{
   131  			desc:   errdesc,
   132  			err:    err,
   133  			output: output,
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func runSync(args ...string) error {
   141  	return runCmd(exec.Command("sync", args...), "sync")
   142  }
   143  
   144  func runCpPreserveAll(path, dest, errdesc string) error {
   145  	return runCmd(exec.Command("cp", "-av", path, dest), errdesc)
   146  }
   147  
   148  // CopySpecialFile is used to copy all the things that are not files
   149  // (like device nodes, named pipes etc)
   150  func CopySpecialFile(path, dest string) error {
   151  	if err := runCpPreserveAll(path, dest, "copy device node"); err != nil {
   152  		return err
   153  	}
   154  	return runSync(filepath.Dir(dest))
   155  }
   156  
   157  // CopySpecialFileError is returned if a special file copy fails
   158  type CopySpecialFileError struct {
   159  	desc     string
   160  	exitCode int
   161  	output   []byte
   162  	err      error
   163  }
   164  
   165  func (e CopySpecialFileError) Error() string {
   166  	if e.err == nil {
   167  		return fmt.Sprintf("failed to %s: %q (%v)", e.desc, e.output, e.exitCode)
   168  	}
   169  
   170  	return fmt.Sprintf("failed to %s: %q (%v)", e.desc, e.output, e.err)
   171  }