github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  // AtomicWriteFileCopy writes to dst a copy of src using AtomicFile
   121  // internally to create the destination.
   122  // The destination path is always overwritten. The destination and
   123  // the owning directory are synced after copy completes. Pass additional flags
   124  // for AtomicFile wrapping the destination.
   125  func AtomicWriteFileCopy(dst, src string, flags AtomicWriteFlags) (err error) {
   126  	fin, err := openfile(src, os.O_RDONLY, 0)
   127  	if err != nil {
   128  		return fmt.Errorf("unable to open source file %s: %v", src, err)
   129  	}
   130  	defer func() {
   131  		if cerr := fin.Close(); cerr != nil && err == nil {
   132  			err = fmt.Errorf("when closing %s: %v", src, cerr)
   133  		}
   134  	}()
   135  
   136  	fi, err := fin.Stat()
   137  	if err != nil {
   138  		return fmt.Errorf("unable to stat %s: %v", src, err)
   139  	}
   140  
   141  	fout, err := NewAtomicFile(dst, fi.Mode(), flags, NoChown, NoChown)
   142  	if err != nil {
   143  		return fmt.Errorf("cannot create atomic file: %v", err)
   144  	}
   145  	defer func() {
   146  		if cerr := fout.Cancel(); cerr != ErrCannotCancel && err == nil {
   147  			err = fmt.Errorf("cannot cancel temporary file copy %s: %v", fout.Name(), cerr)
   148  		}
   149  	}()
   150  
   151  	if err := copyfile(fin, fout, fi); err != nil {
   152  		return fmt.Errorf("unable to copy %s to %s: %v", src, fout.Name(), err)
   153  	}
   154  
   155  	if err := fout.Commit(); err != nil {
   156  		return fmt.Errorf("cannot commit atomic file copy: %v", err)
   157  	}
   158  	return nil
   159  }
   160  
   161  func runCmd(cmd *exec.Cmd, errdesc string) error {
   162  	if output, err := cmd.CombinedOutput(); err != nil {
   163  		output = bytes.TrimSpace(output)
   164  		if exitCode, err := ExitCode(err); err == nil {
   165  			return &CopySpecialFileError{
   166  				desc:     errdesc,
   167  				exitCode: exitCode,
   168  				output:   output,
   169  			}
   170  		}
   171  		return &CopySpecialFileError{
   172  			desc:   errdesc,
   173  			err:    err,
   174  			output: output,
   175  		}
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func runSync(args ...string) error {
   182  	return runCmd(exec.Command("sync", args...), "sync")
   183  }
   184  
   185  func runCpPreserveAll(path, dest, errdesc string) error {
   186  	return runCmd(exec.Command("cp", "-av", path, dest), errdesc)
   187  }
   188  
   189  // CopySpecialFile is used to copy all the things that are not files
   190  // (like device nodes, named pipes etc)
   191  func CopySpecialFile(path, dest string) error {
   192  	if err := runCpPreserveAll(path, dest, "copy device node"); err != nil {
   193  		return err
   194  	}
   195  	return runSync(filepath.Dir(dest))
   196  }
   197  
   198  // CopySpecialFileError is returned if a special file copy fails
   199  type CopySpecialFileError struct {
   200  	desc     string
   201  	exitCode int
   202  	output   []byte
   203  	err      error
   204  }
   205  
   206  func (e CopySpecialFileError) Error() string {
   207  	if e.err == nil {
   208  		return fmt.Sprintf("failed to %s: %q (%v)", e.desc, e.output, e.exitCode)
   209  	}
   210  
   211  	return fmt.Sprintf("failed to %s: %q (%v)", e.desc, e.output, e.err)
   212  }