backup.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. //go:build windows
  2. // +build windows
  3. package winio
  4. import (
  5. "encoding/binary"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "runtime"
  11. "syscall"
  12. "unicode/utf16"
  13. "golang.org/x/sys/windows"
  14. )
  15. //sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
  16. //sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
  17. const (
  18. BackupData = uint32(iota + 1)
  19. BackupEaData
  20. BackupSecurity
  21. BackupAlternateData
  22. BackupLink
  23. BackupPropertyData
  24. BackupObjectId //revive:disable-line:var-naming ID, not Id
  25. BackupReparseData
  26. BackupSparseBlock
  27. BackupTxfsData
  28. )
  29. const (
  30. StreamSparseAttributes = uint32(8)
  31. )
  32. //nolint:revive // var-naming: ALL_CAPS
  33. const (
  34. WRITE_DAC = windows.WRITE_DAC
  35. WRITE_OWNER = windows.WRITE_OWNER
  36. ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY
  37. )
  38. // BackupHeader represents a backup stream of a file.
  39. type BackupHeader struct {
  40. //revive:disable-next-line:var-naming ID, not Id
  41. Id uint32 // The backup stream ID
  42. Attributes uint32 // Stream attributes
  43. Size int64 // The size of the stream in bytes
  44. Name string // The name of the stream (for BackupAlternateData only).
  45. Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
  46. }
  47. type win32StreamID struct {
  48. StreamID uint32
  49. Attributes uint32
  50. Size uint64
  51. NameSize uint32
  52. }
  53. // BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
  54. // of BackupHeader values.
  55. type BackupStreamReader struct {
  56. r io.Reader
  57. bytesLeft int64
  58. }
  59. // NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
  60. func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
  61. return &BackupStreamReader{r, 0}
  62. }
  63. // Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
  64. // it was not completely read.
  65. func (r *BackupStreamReader) Next() (*BackupHeader, error) {
  66. if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this
  67. if s, ok := r.r.(io.Seeker); ok {
  68. // Make sure Seek on io.SeekCurrent sometimes succeeds
  69. // before trying the actual seek.
  70. if _, err := s.Seek(0, io.SeekCurrent); err == nil {
  71. if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
  72. return nil, err
  73. }
  74. r.bytesLeft = 0
  75. }
  76. }
  77. if _, err := io.Copy(io.Discard, r); err != nil {
  78. return nil, err
  79. }
  80. }
  81. var wsi win32StreamID
  82. if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
  83. return nil, err
  84. }
  85. hdr := &BackupHeader{
  86. Id: wsi.StreamID,
  87. Attributes: wsi.Attributes,
  88. Size: int64(wsi.Size),
  89. }
  90. if wsi.NameSize != 0 {
  91. name := make([]uint16, int(wsi.NameSize/2))
  92. if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
  93. return nil, err
  94. }
  95. hdr.Name = syscall.UTF16ToString(name)
  96. }
  97. if wsi.StreamID == BackupSparseBlock {
  98. if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
  99. return nil, err
  100. }
  101. hdr.Size -= 8
  102. }
  103. r.bytesLeft = hdr.Size
  104. return hdr, nil
  105. }
  106. // Read reads from the current backup stream.
  107. func (r *BackupStreamReader) Read(b []byte) (int, error) {
  108. if r.bytesLeft == 0 {
  109. return 0, io.EOF
  110. }
  111. if int64(len(b)) > r.bytesLeft {
  112. b = b[:r.bytesLeft]
  113. }
  114. n, err := r.r.Read(b)
  115. r.bytesLeft -= int64(n)
  116. if err == io.EOF {
  117. err = io.ErrUnexpectedEOF
  118. } else if r.bytesLeft == 0 && err == nil {
  119. err = io.EOF
  120. }
  121. return n, err
  122. }
  123. // BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
  124. type BackupStreamWriter struct {
  125. w io.Writer
  126. bytesLeft int64
  127. }
  128. // NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
  129. func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
  130. return &BackupStreamWriter{w, 0}
  131. }
  132. // WriteHeader writes the next backup stream header and prepares for calls to Write().
  133. func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
  134. if w.bytesLeft != 0 {
  135. return fmt.Errorf("missing %d bytes", w.bytesLeft)
  136. }
  137. name := utf16.Encode([]rune(hdr.Name))
  138. wsi := win32StreamID{
  139. StreamID: hdr.Id,
  140. Attributes: hdr.Attributes,
  141. Size: uint64(hdr.Size),
  142. NameSize: uint32(len(name) * 2),
  143. }
  144. if hdr.Id == BackupSparseBlock {
  145. // Include space for the int64 block offset
  146. wsi.Size += 8
  147. }
  148. if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
  149. return err
  150. }
  151. if len(name) != 0 {
  152. if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
  153. return err
  154. }
  155. }
  156. if hdr.Id == BackupSparseBlock {
  157. if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
  158. return err
  159. }
  160. }
  161. w.bytesLeft = hdr.Size
  162. return nil
  163. }
  164. // Write writes to the current backup stream.
  165. func (w *BackupStreamWriter) Write(b []byte) (int, error) {
  166. if w.bytesLeft < int64(len(b)) {
  167. return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
  168. }
  169. n, err := w.w.Write(b)
  170. w.bytesLeft -= int64(n)
  171. return n, err
  172. }
  173. // BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
  174. type BackupFileReader struct {
  175. f *os.File
  176. includeSecurity bool
  177. ctx uintptr
  178. }
  179. // NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
  180. // Read will attempt to read the security descriptor of the file.
  181. func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
  182. r := &BackupFileReader{f, includeSecurity, 0}
  183. return r
  184. }
  185. // Read reads a backup stream from the file by calling the Win32 API BackupRead().
  186. func (r *BackupFileReader) Read(b []byte) (int, error) {
  187. var bytesRead uint32
  188. err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
  189. if err != nil {
  190. return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
  191. }
  192. runtime.KeepAlive(r.f)
  193. if bytesRead == 0 {
  194. return 0, io.EOF
  195. }
  196. return int(bytesRead), nil
  197. }
  198. // Close frees Win32 resources associated with the BackupFileReader. It does not close
  199. // the underlying file.
  200. func (r *BackupFileReader) Close() error {
  201. if r.ctx != 0 {
  202. _ = backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
  203. runtime.KeepAlive(r.f)
  204. r.ctx = 0
  205. }
  206. return nil
  207. }
  208. // BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
  209. type BackupFileWriter struct {
  210. f *os.File
  211. includeSecurity bool
  212. ctx uintptr
  213. }
  214. // NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
  215. // Write() will attempt to restore the security descriptor from the stream.
  216. func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
  217. w := &BackupFileWriter{f, includeSecurity, 0}
  218. return w
  219. }
  220. // Write restores a portion of the file using the provided backup stream.
  221. func (w *BackupFileWriter) Write(b []byte) (int, error) {
  222. var bytesWritten uint32
  223. err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
  224. if err != nil {
  225. return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
  226. }
  227. runtime.KeepAlive(w.f)
  228. if int(bytesWritten) != len(b) {
  229. return int(bytesWritten), errors.New("not all bytes could be written")
  230. }
  231. return len(b), nil
  232. }
  233. // Close frees Win32 resources associated with the BackupFileWriter. It does not
  234. // close the underlying file.
  235. func (w *BackupFileWriter) Close() error {
  236. if w.ctx != 0 {
  237. _ = backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
  238. runtime.KeepAlive(w.f)
  239. w.ctx = 0
  240. }
  241. return nil
  242. }
  243. // OpenForBackup opens a file or directory, potentially skipping access checks if the backup
  244. // or restore privileges have been acquired.
  245. //
  246. // If the file opened was a directory, it cannot be used with Readdir().
  247. func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
  248. winPath, err := syscall.UTF16FromString(path)
  249. if err != nil {
  250. return nil, err
  251. }
  252. h, err := syscall.CreateFile(&winPath[0],
  253. access,
  254. share,
  255. nil,
  256. createmode,
  257. syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT,
  258. 0)
  259. if err != nil {
  260. err = &os.PathError{Op: "open", Path: path, Err: err}
  261. return nil, err
  262. }
  263. return os.NewFile(uintptr(h), path), nil
  264. }