This repository has been archived by the owner on Aug 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdiskutil_windows.go
182 lines (158 loc) · 3.94 KB
/
diskutil_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package diskutil
import (
"io"
"io/ioutil"
"os/exec"
"regexp"
"strconv"
"syscall"
"time"
"github.com/pkg/errors"
)
var diskNoRe = regexp.MustCompile(`(\\\\\.\\PHYSICALDRIVE)(\d+)`)
// CleanDisk uses diskutil to clean a drive by number.
func CleanDisk(diskNum int) error {
// Run diskpart script:
// - select disk 3
// - clean
// - rescan
scrFile, err := ioutil.TempFile("", "*.txt")
if err != nil {
return errors.Wrap(err, "open temporary file")
}
scrFilePath := scrFile.Name()
scrFile.WriteString("select disk ")
scrFile.WriteString(strconv.Itoa(diskNum))
scrFile.WriteString("\nclean\nrescan\n")
if err := scrFile.Close(); err != nil {
return errors.Wrap(err, "close temporary file")
}
// requires elevation
diskPart := exec.Command("diskpart", "/s", scrFilePath)
if err := diskPart.Run(); err != nil {
return errors.Wrap(err, "diskpart clean disk")
}
return nil
}
// FlashToDisk clears a disk and flashes a image.
// Progress callback is called with updates.
// DiskPath on windows is expected to be \\.\PHYSICALDRIVE{N}
func FlashToDisk(
image io.Reader,
imageSize uint64, // imageSize in bytes for progress, if zero, disables progress.
diskPath string,
progressCb func(percent int, status string),
) error {
imageSizeF := float64(imageSize)
// Read the first chunk, we want to defer writing this.
const chunkSize = 65536
var firstChunk [chunkSize]byte
if _, err := io.ReadAtLeast(image, firstChunk[:], chunkSize); err != nil {
return errors.Wrap(err, "read first chunk")
}
diskNum, err := getDiskNumber(diskPath)
if err != nil {
return err
}
progressCb(5, "Cleaning disk...")
if err := CleanDisk(diskNum); err != nil {
return err
}
// wait 2 seconds for partitions to update
for i := 0; i < 10; i++ {
progressCb(10+i, "Windows rescanning disk...")
<-time.After(time.Millisecond * 200)
}
progressCb(23, "Opening and locking disk...")
f, err := OpenDiskRaw(diskPath)
if err != nil {
return err
}
defer syscall.CloseHandle(f)
progressCb(25, "Initializing transfer...")
// Pipe the rest.
chunkBuf := make([]byte, chunkSize)
writeOffset := int64(chunkSize)
lastProgressUpdate := 0
writeProgress := func() {
lastProgressUpdate = 0
// compute percent written
// maximum offset is
percent := uint32(25) + uint32((float64(writeOffset)/imageSizeF)*float64(75))
progressCb(int(percent), "Flashing image to disk...")
}
if _, err := syscall.SetFilePointer(f, chunkSize, nil, syscall.FILE_BEGIN); err != nil {
return err
}
for {
nr, er := image.Read(chunkBuf)
if nr > 0 && nr < chunkSize {
zeroSeg := chunkBuf[nr:]
for i := range zeroSeg {
zeroSeg[i] = 0
}
}
if nr > 0 {
var nw uint32
ew := syscall.WriteFile(f, chunkBuf, &nw, nil)
if ew != nil {
err = ew
break
}
if chunkSize != int(nw) {
err = io.ErrShortWrite
break
}
writeOffset += chunkSize
if imageSize != 0 {
lastProgressUpdate++
if lastProgressUpdate == 15 {
writeProgress()
}
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
if err != nil {
return errors.Wrap(err, "copy image to disk")
}
if _, err := syscall.SetFilePointer(f, 0, nil, syscall.FILE_BEGIN); err != nil {
return err
}
{
var nw uint32
ew := syscall.WriteFile(f, firstChunk[:], &nw, nil)
if ew != nil {
return errors.Wrap(ew, "write header chunk to disk")
}
}
progressCb(100, "Done flashing.")
return nil
}
// getDiskNumber matches the disk number in a PHYSICALDRIVE path.
func getDiskNumber(diskPath string) (int, error) {
matches := diskNoRe.FindStringSubmatch(diskPath)
if len(matches) != 3 {
return 0, errors.Errorf(
"not windows physicaldrive path: %s",
diskPath,
)
}
d, err := strconv.Atoi(matches[2])
if err != nil {
return 0, errors.Errorf(
"windows physicaldrive path mismatch: %s %v",
diskPath,
err,
)
}
if d == 0 {
return 0, errors.Errorf("physicaldrive 0 selected, this is probably in error: %v", matches)
}
return d, nil
}