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