gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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 }