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 }