1 /*
2 * Copyright 2014-2017 Frank Hunleth
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #if defined(_WIN32) || defined(__CYGWIN__)
18
19 #define UNICODE
20 #include <windows.h>
21 #include <io.h>
22 #include <stdlib.h>
23 #include <wchar.h>
24
25 #include "mmc.h"
26 #include "util.h"
27
mmc_init()28 void mmc_init()
29 {
30 }
31
mmc_finalize()32 void mmc_finalize()
33 {
34 }
35
36 /**
37 * @brief Scan for SDCards and other removable media
38 * @param devices where to store detected devices and some metadata
39 * @param max_devices the max to return
40 * @return the number of devices found
41 */
mmc_scan_for_devices(struct mmc_device * devices,int max_devices)42 int mmc_scan_for_devices(struct mmc_device *devices, int max_devices)
43 {
44 memset(devices, 0, max_devices * sizeof(struct mmc_device));
45
46 // There's not an API to request all the PhysicalDrives, but they are
47 // sequentially enumerated and become invalid if they're removed.
48 // So we just scan the first 256 of them until we find enough matches
49 int device_count = 0;
50 for (int i = 0; i < 256 && device_count < max_devices ; i++) {
51 WCHAR drive_path[MAX_PATH] = L"";
52 wsprintf(drive_path, L"\\\\.\\PhysicalDrive%d", i);
53
54 HANDLE drive_handle;
55 drive_handle = CreateFile(drive_path,
56 GENERIC_READ | GENERIC_WRITE,
57 FILE_SHARE_READ | FILE_SHARE_WRITE,
58 NULL,
59 OPEN_EXISTING,
60 0,
61 NULL);
62
63 if (drive_handle == INVALID_HANDLE_VALUE) {
64 CloseHandle(drive_handle);
65 continue;
66 }
67
68 DWORD bytes_returned;
69 DISK_GEOMETRY_EX geometry;
70 BOOL status = DeviceIoControl(drive_handle,
71 IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
72 NULL,
73 0,
74 &geometry,
75 sizeof(geometry),
76 &bytes_returned,
77 NULL);
78 CloseHandle(drive_handle);
79
80 if (status && geometry.Geometry.MediaType == RemovableMedia && geometry.DiskSize.QuadPart > 0) {
81 struct mmc_device *device;
82 device = &devices[device_count];
83 WideCharToMultiByte(CP_UTF8, 0, drive_path, -1, device->path, sizeof(device->path), NULL, NULL);
84 device->size = geometry.DiskSize.QuadPart;
85 device_count++;
86 }
87 }
88
89 return device_count;
90 }
91
92 /*
93 * @brief Query the Physical Drive(s) that back a Logical Volume
94 * Since we're trying dealing with SD cards, ignore any
95 * volumes that span multiple Physical Drives.
96 * @param volume_name the name of the volume as a Wide String
97 * @return the PhysicalDrive Number on success, 0 on failure
98 */
query_physical_extents(LPWSTR volume_name)99 static unsigned int query_physical_extents(LPWSTR volume_name) {
100 // We have to remove the trailing slash for this API call
101 volume_name[wcslen(volume_name) - 1] = L'\0';
102 HANDLE volume_handle = CreateFile(volume_name,
103 GENERIC_READ | GENERIC_WRITE,
104 FILE_SHARE_READ | FILE_SHARE_WRITE,
105 NULL,
106 OPEN_EXISTING,
107 0,
108 NULL);
109 if (volume_handle == INVALID_HANDLE_VALUE) {
110 return 0;
111 }
112
113 VOLUME_DISK_EXTENTS extents;
114 DWORD bytes_returned;
115 BOOL status = DeviceIoControl(volume_handle,
116 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
117 NULL,
118 0,
119 &extents,
120 sizeof(extents),
121 &bytes_returned,
122 NULL);
123 CloseHandle(volume_handle);
124
125 if (!status || extents.NumberOfDiskExtents > 1) {
126 // Ignore it if we can't query its extents or it is backed by
127 // more than one extent because it's probably not an SD card.
128 return 0;
129 }
130
131 return extents.Extents[0].DiskNumber;
132 }
133
134 /*
135 * @brief Attempt to unmount the specified volume, exit with failure message if unsuccessful
136 * @param volume_name the name of the volume as a Wide String
137 */
unmount_volume(LPWSTR volume_name)138 static void unmount_volume(LPWSTR volume_name) {
139 HANDLE volume_handle = CreateFile(volume_name,
140 GENERIC_READ | GENERIC_WRITE,
141 FILE_SHARE_READ | FILE_SHARE_WRITE,
142 NULL,
143 OPEN_EXISTING,
144 0,
145 NULL);
146 if (volume_handle == INVALID_HANDLE_VALUE)
147 fwup_errx(EXIT_FAILURE, "Could not open '%S' for unmounting (Error %lu)", volume_name, GetLastError());
148
149 DWORD bytes_returned;
150 BOOL status = DeviceIoControl(volume_handle,
151 FSCTL_LOCK_VOLUME,
152 NULL,
153 0,
154 NULL,
155 0,
156 &bytes_returned,
157 NULL);
158
159 if (!status)
160 fwup_warnx("Could not lock '%S' for unmounting (Error %lu)", volume_name, GetLastError());
161
162 status = DeviceIoControl(volume_handle,
163 FSCTL_DISMOUNT_VOLUME,
164 NULL,
165 0,
166 NULL,
167 0,
168 &bytes_returned,
169 NULL);
170
171
172 if (!status)
173 fwup_errx(EXIT_FAILURE, "Error unmounting '%S' (Error %lu)", volume_name, GetLastError());
174
175 // Note we deliberately do not FSCTL_UNLOCK_VOLUME or call CloseHandle, as the Logical Volume must
176 // remained locked. We rely on Windows to cleanup for us
177 }
178
179 /*
180 * @brief Unmount all LogicalVolumes using the specified PhysicalDrive
181 * @param mmc_device the name of the PhysicalDrive
182 * @return 0 on success, exit the program with a failure message otherwise
183 */
mmc_umount_all(const char * mmc_device)184 int mmc_umount_all(const char *mmc_device)
185 {
186 unsigned int target_disk_number = 0;
187 sscanf(mmc_device, "\\\\.\\PhysicalDrive%u", &target_disk_number);
188 if (target_disk_number == 0)
189 fwup_errx(EXIT_FAILURE, "Target device must be formatted like \\\\.\\PhysicalDisk# where # is a positive integer.");
190
191 WCHAR volume_name[MAX_PATH] = L"";
192 HANDLE volume_iter = FindFirstVolume(volume_name, ARRAYSIZE(volume_name));
193
194 if (volume_iter == INVALID_HANDLE_VALUE)
195 fwup_errx(EXIT_FAILURE, "Can't enumerate logical volumes (Error %lu)", GetLastError());
196
197 do {
198 unsigned int disk_number = query_physical_extents(volume_name);
199 if (disk_number == target_disk_number)
200 unmount_volume(volume_name);
201 } while (FindNextVolume(volume_iter, volume_name, ARRAYSIZE(volume_name)));
202
203 FindVolumeClose(volume_iter);
204
205 return 0;
206 }
207
mmc_eject(const char * mmc_device)208 int mmc_eject(const char *mmc_device)
209 {
210 WCHAR drive_path[MAX_PATH] = L"";
211 MultiByteToWideChar(CP_UTF8, 0, mmc_device, -1, drive_path, sizeof(drive_path));
212
213 HANDLE drive_handle;
214 drive_handle = CreateFile(drive_path,
215 GENERIC_READ | GENERIC_WRITE,
216 FILE_SHARE_READ | FILE_SHARE_WRITE,
217 NULL,
218 OPEN_EXISTING,
219 0,
220 NULL);
221
222 if (drive_handle == INVALID_HANDLE_VALUE)
223 fwup_errx(EXIT_FAILURE, "Error re-opening'%S' (Error %lu)", drive_path, GetLastError());
224
225 BOOL status = DeviceIoControl(drive_handle,
226 IOCTL_STORAGE_EJECT_MEDIA,
227 NULL,
228 0,
229 NULL,
230 0,
231 NULL,
232 NULL);
233 CloseHandle(drive_handle);
234 if (!status)
235 fwup_errx(EXIT_FAILURE, "Error ejecting '%S' (Error %lu)", drive_path, GetLastError());
236
237 return 0;
238 }
239
mmc_device_size(const char * mmc_path,off_t * end_offset)240 int mmc_device_size(const char *mmc_path, off_t *end_offset)
241 {
242 WCHAR drive_name[MAX_PATH] = L"";
243 MultiByteToWideChar(CP_UTF8, 0, mmc_path, -1, drive_name, MAX_PATH);
244
245 HANDLE drive_handle = CreateFile(drive_name,
246 GENERIC_READ | GENERIC_WRITE,
247 FILE_SHARE_READ | FILE_SHARE_WRITE,
248 NULL,
249 OPEN_EXISTING,
250 FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
251 NULL);
252 if (drive_handle == INVALID_HANDLE_VALUE) {
253 *end_offset = 0;
254 return -1;
255 }
256
257 DWORD bytes_returned;
258 DISK_GEOMETRY_EX geometry;
259 BOOL status = DeviceIoControl(drive_handle,
260 IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
261 NULL,
262 0,
263 &geometry,
264 sizeof(geometry),
265 &bytes_returned,
266 NULL);
267 CloseHandle(drive_handle);
268
269 if (status && geometry.DiskSize.QuadPart > 0) {
270 *end_offset = geometry.DiskSize.QuadPart;
271 return 0;
272 } else {
273 *end_offset = 0;
274 return -1;
275 }
276 }
277
278 /**
279 * @brief Open an SDCard/MMC device
280 * @param mmc_path the path
281 * @return a filehandle or <0 on error
282 */
mmc_open(const char * mmc_path)283 int mmc_open(const char *mmc_path)
284 {
285 WCHAR drive_name[MAX_PATH] = L"";
286 MultiByteToWideChar(CP_UTF8, 0, mmc_path, -1, drive_name, MAX_PATH);
287
288 HANDLE drive_handle = CreateFile(drive_name,
289 GENERIC_READ | GENERIC_WRITE,
290 FILE_SHARE_READ | FILE_SHARE_WRITE,
291 NULL,
292 OPEN_EXISTING,
293 FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
294 NULL);
295 if (drive_handle == INVALID_HANDLE_VALUE) {
296 fwup_warnx("Unable to open drive %S\n (%lu)", drive_name, GetLastError());
297 return -1;
298 }
299
300 return _open_osfhandle((intptr_t) drive_handle, 0);
301 }
302
mmc_is_path_on_device(const char * file_path,const char * device_path)303 int mmc_is_path_on_device(const char *file_path, const char *device_path)
304 {
305 // Not implemented - I don't think there's a use case for this on Windows.
306 return -1;
307 }
308
mmc_is_path_at_device_offset(const char * file_path,off_t block_offset)309 int mmc_is_path_at_device_offset(const char *file_path, off_t block_offset)
310 {
311 // Not implemented - I don't think there's a use case for this on Mac.
312 return -1;
313 }
314
mmc_trim(int fd,off_t offset,off_t count)315 int mmc_trim(int fd, off_t offset, off_t count)
316 {
317 // Not implemented
318 fwup_warnx("TRIM command not implemented.");
319 (void) fd;
320 (void) offset;
321 (void) count;
322 return 0;
323 }
324 #endif // defined(_WIN32) || defined(__CYGWIN__)
325