1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson 2017-2018. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 
21 #include "erl_nif.h"
22 #include "config.h"
23 #include "sys.h"
24 
25 #include "prim_file_nif.h"
26 
27 #include <winioctl.h>
28 #include <windows.h>
29 #include <strsafe.h>
30 #include <wchar.h>
31 
32 #define IS_SLASH(a)  ((a) == L'\\' || (a) == L'/')
33 
34 #define FILE_SHARE_FLAGS (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
35 
36 /* Long paths can either be in the file (?) or the device (.) namespace. UNC
37  * paths are always in the file namespace. */
38 #define LP_FILE_PREFIX L"\\\\?\\"
39 #define LP_DEV_PREFIX L"\\\\.\\"
40 #define LP_UNC_PREFIX (LP_FILE_PREFIX L"UNC\\")
41 
42 #define LP_PREFIX_SIZE (sizeof(LP_FILE_PREFIX) - sizeof(WCHAR))
43 #define LP_PREFIX_LENGTH (LP_PREFIX_SIZE / sizeof(WCHAR))
44 
45 #define LP_UNC_PREFIX_SIZE (sizeof(LP_UNC_PREFIX) - sizeof(WCHAR))
46 #define LP_UNC_PREFIX_LENGTH (LP_UNC_PREFIX_SIZE / sizeof(WCHAR))
47 
48 #define IS_LONG_PATH(length, data) \
49     ((length) >= LP_PREFIX_LENGTH && \
50          (!sys_memcmp((data), LP_FILE_PREFIX, LP_PREFIX_SIZE) || \
51           !sys_memcmp((data), LP_DEV_PREFIX, LP_PREFIX_SIZE)))
52 
53 #define IS_LONG_UNC_PATH(length, data) \
54     ((length) >= LP_UNC_PREFIX_LENGTH && \
55          !sys_memcmp((data), LP_UNC_PREFIX, LP_UNC_PREFIX_SIZE))
56 
57 #define PATH_LENGTH(path) (path->size / sizeof(WCHAR) - 1)
58 
59 #define ASSERT_PATH_FORMAT(path) \
60     do { \
61         ASSERT(IS_LONG_PATH(PATH_LENGTH(path), (path)->data)); \
62         ASSERT(PATH_LENGTH(path) == wcslen((WCHAR*)path->data)); \
63     } while(0)
64 
65 #define TICKS_PER_SECOND (10000000ULL)
66 #define EPOCH_DIFFERENCE (11644473600LL)
67 
68 #define FILETIME_TO_EPOCH(epoch, ft) \
69     do { \
70         ULARGE_INTEGER ull; \
71         ull.LowPart  = (ft).dwLowDateTime; \
72         ull.HighPart = (ft).dwHighDateTime; \
73         (epoch) = ((ull.QuadPart / TICKS_PER_SECOND) - EPOCH_DIFFERENCE); \
74     } while(0)
75 
76 #define EPOCH_TO_FILETIME(ft, epoch) \
77     do { \
78         ULARGE_INTEGER ull; \
79         ull.QuadPart = (((epoch) + EPOCH_DIFFERENCE) * TICKS_PER_SECOND); \
80         (ft).dwLowDateTime = ull.LowPart; \
81         (ft).dwHighDateTime = ull.HighPart; \
82     } while(0)
83 
84 typedef struct {
85     efile_data_t common;
86     HANDLE handle;
87 } efile_win_t;
88 
89 static int windows_to_posix_errno(DWORD last_error);
90 
has_invalid_null_termination(const ErlNifBinary * path)91 static int has_invalid_null_termination(const ErlNifBinary *path) {
92     const WCHAR *null_pos, *end_pos;
93 
94     null_pos = wmemchr((const WCHAR*)path->data, L'\0', path->size);
95     end_pos = (const WCHAR*)&path->data[path->size] - 1;
96 
97     if(null_pos == NULL) {
98         return 1;
99     }
100 
101     /* prim_file:internal_name2native sometimes feeds us data that is "doubly"
102      * NUL-terminated, so we'll accept any number of trailing NULs so long as
103      * they aren't interrupted by anything else. */
104     while(null_pos < end_pos && (*null_pos) == L'\0') {
105         null_pos++;
106     }
107 
108     return null_pos != end_pos;
109 }
110 
get_full_path(ErlNifEnv * env,WCHAR * input,efile_path_t * result)111 static posix_errno_t get_full_path(ErlNifEnv *env, WCHAR *input, efile_path_t *result) {
112     DWORD maximum_length, actual_length;
113     int add_long_prefix;
114 
115     maximum_length = GetFullPathNameW(input, 0, NULL, NULL);
116     add_long_prefix = 0;
117 
118     if(maximum_length == 0) {
119         /* POSIX doesn't have the concept of a "path error" in the same way
120          * Windows does, so we'll return ENOENT since that's what most POSIX
121          * APIs would return if they were fed such garbage. */
122         return ENOENT;
123     }
124 
125     maximum_length += MAX(LP_PREFIX_LENGTH, LP_UNC_PREFIX_LENGTH);
126 
127     if(!enif_alloc_binary(maximum_length * sizeof(WCHAR), result)) {
128         return ENOMEM;
129     }
130 
131     actual_length = GetFullPathNameW(input, maximum_length, (WCHAR*)result->data, NULL);
132 
133     if(actual_length < maximum_length) {
134         int is_long_path, maybe_unc_path;
135         WCHAR *path_start;
136 
137         /* The APIs we use have varying path length limits and sometimes
138          * behave differently when given a long-path prefix, so it's simplest
139          * to always use long paths. */
140 
141         is_long_path = IS_LONG_PATH(actual_length, result->data);
142         maybe_unc_path = !sys_memcmp(result->data, L"\\\\", sizeof(WCHAR) * 2);
143 
144         if(maybe_unc_path && !is_long_path) {
145             /* \\localhost\c$\gurka -> \\?\UNC\localhost\c$\gurka */
146             sys_memmove(result->data + LP_UNC_PREFIX_SIZE,
147                 &((WCHAR*)result->data)[2],
148                 (actual_length - 1) * sizeof(WCHAR));
149             sys_memcpy(result->data, LP_UNC_PREFIX, LP_UNC_PREFIX_SIZE);
150             actual_length += LP_UNC_PREFIX_LENGTH;
151         } else if(!is_long_path) {
152             /* C:\gurka -> \\?\C:\gurka */
153             sys_memmove(result->data + LP_PREFIX_SIZE, result->data,
154                 (actual_length + 1) * sizeof(WCHAR));
155             sys_memcpy(result->data, LP_FILE_PREFIX, LP_PREFIX_SIZE);
156             actual_length += LP_PREFIX_LENGTH;
157         }
158 
159         path_start = (WCHAR*)result->data;
160 
161         /* We're removing trailing slashes since quite a few APIs refuse to
162          * work with them, and none require them. We only check the last
163          * character since GetFullPathNameW folds slashes together. */
164         if(IS_SLASH(path_start[actual_length - 1])) {
165             if(path_start[actual_length - 2] != L':') {
166                 path_start[actual_length - 1] = L'\0';
167                 actual_length--;
168             }
169         }
170 
171         if(!enif_realloc_binary(result, (actual_length + 1) * sizeof(WCHAR))) {
172             enif_release_binary(result);
173             return ENOMEM;
174         }
175 
176         enif_make_binary(env, result);
177         return 0;
178     }
179 
180     /* We may end up here if the current directory changes to something longer
181      * between/during GetFullPathName. There's nothing sensible we can do about
182      * this. */
183 
184     enif_release_binary(result);
185 
186     return EINVAL;
187 }
188 
efile_marshal_path(ErlNifEnv * env,ERL_NIF_TERM path,efile_path_t * result)189 posix_errno_t efile_marshal_path(ErlNifEnv *env, ERL_NIF_TERM path, efile_path_t *result) {
190     ErlNifBinary raw_path;
191 
192     if(!enif_inspect_binary(env, path, &raw_path)) {
193         return EINVAL;
194     } else if(raw_path.size % sizeof(WCHAR)) {
195         return EINVAL;
196     }
197 
198     if(has_invalid_null_termination(&raw_path)) {
199         return EINVAL;
200     }
201 
202     return get_full_path(env, (WCHAR*)raw_path.data, result);
203 }
204 
efile_get_handle(ErlNifEnv * env,efile_data_t * d)205 ERL_NIF_TERM efile_get_handle(ErlNifEnv *env, efile_data_t *d) {
206     efile_win_t *w = (efile_win_t*)d;
207 
208     ERL_NIF_TERM result;
209     unsigned char *bits;
210 
211     bits = enif_make_new_binary(env, sizeof(w->handle), &result);
212     memcpy(bits, &w->handle, sizeof(w->handle));
213 
214     return result;
215 }
216 
217 /** @brief Converts a native path to the preferred form in "erlang space,"
218  * without path-prefixes, forward-slashes, or NUL terminators. */
normalize_path_result(ErlNifBinary * path)219 static int normalize_path_result(ErlNifBinary *path) {
220     WCHAR *path_iterator, *path_start, *path_end;
221     int length;
222 
223     path_start = (WCHAR*)path->data;
224     length = wcslen(path_start);
225 
226     ASSERT(length < path->size / sizeof(WCHAR));
227 
228     /* Get rid of the long-path prefix, if present. */
229 
230     if(IS_LONG_UNC_PATH(length, path_start)) {
231         /* The first two characters (\\) are the same for both long and short
232          * UNC paths. */
233         sys_memmove(&path_start[2], &path_start[LP_UNC_PREFIX_LENGTH],
234             (length - LP_UNC_PREFIX_LENGTH) * sizeof(WCHAR));
235 
236         length -= LP_UNC_PREFIX_LENGTH - 2;
237     } else if(IS_LONG_PATH(length, path_start)) {
238         length -= LP_PREFIX_LENGTH;
239 
240         sys_memmove(path_start, &path_start[LP_PREFIX_LENGTH],
241             length * sizeof(WCHAR));
242     }
243 
244     path_end = &path_start[length];
245     path_iterator = path_start;
246 
247     /* Convert drive letters to lowercase, if present. */
248     if(length >= 2 && path_start[1] == L':') {
249         WCHAR drive_letter = path_start[0];
250 
251         if(drive_letter >= L'A' && drive_letter <= L'Z') {
252             path_start[0] = drive_letter - L'A' + L'a';
253         }
254     }
255 
256     while(path_iterator < path_end) {
257         if(*path_iterator == L'\\') {
258             *path_iterator = L'/';
259         }
260 
261         path_iterator++;
262     }
263 
264     /* Truncate the result to its actual length; we don't want to include the
265      * NUL terminator. */
266     return enif_realloc_binary(path, length * sizeof(WCHAR));
267 }
268 
269 /* @brief Checks whether all the given attributes are set on the object at the
270  * given path. Note that it assumes false on errors. */
has_file_attributes(const efile_path_t * path,DWORD mask)271 static int has_file_attributes(const efile_path_t *path, DWORD mask) {
272     DWORD attributes = GetFileAttributesW((WCHAR*)path->data);
273 
274     if(attributes == INVALID_FILE_ATTRIBUTES) {
275         return 0;
276     }
277 
278     return !!((attributes & mask) == mask);
279 }
280 
is_ignored_name(int name_length,const WCHAR * name)281 static int is_ignored_name(int name_length, const WCHAR *name) {
282     if(name_length == 1 && name[0] == L'.') {
283         return 1;
284     } else if(name_length == 2 && !sys_memcmp(name, L"..", 2 * sizeof(WCHAR))) {
285         return 1;
286     }
287 
288     return 0;
289 }
290 
get_drive_number(const efile_path_t * path)291 static int get_drive_number(const efile_path_t *path) {
292     const WCHAR *path_start;
293     int length;
294 
295     ASSERT_PATH_FORMAT(path);
296 
297     path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;
298     length = PATH_LENGTH(path) - LP_PREFIX_LENGTH;
299 
300     if(length >= 2 && path_start[1] == L':') {
301         WCHAR drive_letter = path_start[0];
302 
303         if(drive_letter >= L'A' && drive_letter <= L'Z') {
304             return drive_letter - L'A' + 1;
305         } else if(drive_letter >= L'a' && drive_letter <= L'z') {
306             return drive_letter - L'a' + 1;
307         }
308     }
309 
310     return -1;
311 }
312 
313 /* @brief Checks whether two *paths* are on the same mount point; they don't
314  * have to refer to existing or accessible files/directories. */
has_same_mount_point(const efile_path_t * path_a,const efile_path_t * path_b)315 static int has_same_mount_point(const efile_path_t *path_a, const efile_path_t *path_b) {
316     WCHAR *mount_a, *mount_b;
317     int result = 0;
318 
319     mount_a = enif_alloc(path_a->size);
320     mount_b = enif_alloc(path_b->size);
321 
322     if(mount_a != NULL && mount_b != NULL) {
323         int length_a, length_b;
324 
325         length_a = PATH_LENGTH(path_a);
326         length_b = PATH_LENGTH(path_b);
327 
328         if(GetVolumePathNameW((WCHAR*)path_a->data, mount_a, length_a)) {
329             ASSERT(wcslen(mount_a) <= length_a);
330 
331             if(GetVolumePathNameW((WCHAR*)path_b->data, mount_b, length_b)) {
332                 ASSERT(wcslen(mount_b) <= length_b);
333 
334                 result = !_wcsicmp(mount_a, mount_b);
335             }
336         }
337     }
338 
339     if(mount_b != NULL) {
340         enif_free(mount_b);
341     }
342 
343     if(mount_a != NULL) {
344         enif_free(mount_a);
345     }
346 
347     return result;
348 }
349 
350 /* Mirrors the PathIsRootW function of the shell API, but doesn't choke on
351  * paths longer than MAX_PATH. */
is_path_root(const efile_path_t * path)352 static int is_path_root(const efile_path_t *path) {
353     const WCHAR *path_start, *path_end, *path_iterator;
354     int length;
355 
356     ASSERT_PATH_FORMAT(path);
357 
358     if(!IS_LONG_UNC_PATH(PATH_LENGTH(path), path->data)) {
359         path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;
360         length = PATH_LENGTH(path) - LP_PREFIX_LENGTH;
361 
362         /* A single \ refers to the root of the current working directory. */
363         if(length == 1) {
364             return IS_SLASH(path_start[0]);
365         }
366 
367         /* Drive letter. */
368         if(length == 3 && iswalpha(path_start[0]) && path_start[1] == L':') {
369             return IS_SLASH(path_start[2]);
370         }
371 
372         return 0;
373     }
374 
375     /* Check whether we're a UNC root, eg. \\server, \\server\share */
376 
377     path_start = (WCHAR*)path->data + LP_UNC_PREFIX_LENGTH;
378     length = PATH_LENGTH(path) - LP_UNC_PREFIX_LENGTH;
379 
380     path_end = &path_start[length];
381     path_iterator = path_start;
382 
383     /* Server name must be at least one character. */
384     if(length <= 1) {
385         return 0;
386     }
387 
388     /* Slide to the slash between the server and share names, if present. */
389     while(path_iterator < path_end && !IS_SLASH(*path_iterator)) {
390         path_iterator++;
391     }
392 
393     /* Slide past the end of the string, stopping at the first slash we
394      * encounter. */
395     do {
396         path_iterator++;
397     }  while(path_iterator < path_end && !IS_SLASH(*path_iterator));
398 
399     /* If we're past the end of the string and it didnt't end with a slash,
400      * then we're a root path. */
401     return path_iterator >= path_end && !IS_SLASH(path_start[length - 1]);
402 }
403 
efile_open(const efile_path_t * path,enum efile_modes_t modes,ErlNifResourceType * nif_type,efile_data_t ** d)404 posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
405         ErlNifResourceType *nif_type, efile_data_t **d) {
406 
407     DWORD attributes, access_flags, open_mode;
408     HANDLE handle;
409 
410     ASSERT_PATH_FORMAT(path);
411 
412     access_flags = 0;
413     open_mode = 0;
414 
415     if(modes & EFILE_MODE_READ && !(modes & EFILE_MODE_WRITE)) {
416         access_flags = GENERIC_READ;
417         open_mode = OPEN_EXISTING;
418     } else if(modes & EFILE_MODE_WRITE && !(modes & EFILE_MODE_READ)) {
419         access_flags = GENERIC_WRITE;
420         open_mode = CREATE_ALWAYS;
421     } else if(modes & EFILE_MODE_READ_WRITE) {
422         access_flags = GENERIC_READ | GENERIC_WRITE;
423         open_mode = OPEN_ALWAYS;
424     } else {
425         return EINVAL;
426     }
427 
428     if(modes & EFILE_MODE_APPEND) {
429         access_flags |= FILE_APPEND_DATA;
430         open_mode = OPEN_ALWAYS;
431     }
432 
433     if(modes & EFILE_MODE_EXCLUSIVE) {
434         open_mode = CREATE_NEW;
435     }
436 
437     if(modes & EFILE_MODE_SYNC) {
438         attributes = FILE_FLAG_WRITE_THROUGH;
439     } else {
440         attributes = FILE_ATTRIBUTE_NORMAL;
441     }
442 
443     handle = CreateFileW((WCHAR*)path->data, access_flags,
444         FILE_SHARE_FLAGS, NULL, open_mode, attributes, NULL);
445 
446     if(handle != INVALID_HANDLE_VALUE) {
447         efile_win_t *w;
448 
449         w = (efile_win_t*)enif_alloc_resource(nif_type, sizeof(efile_win_t));
450         w->handle = handle;
451 
452         EFILE_INIT_RESOURCE(&w->common, modes);
453         (*d) = &w->common;
454 
455         return 0;
456     } else {
457         DWORD last_error = GetLastError();
458 
459         /* Rewrite all failures on directories to EISDIR to match the old
460          * driver. */
461         if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
462             return EISDIR;
463         }
464 
465         return windows_to_posix_errno(last_error);
466     }
467 }
468 
efile_close(efile_data_t * d,posix_errno_t * error)469 int efile_close(efile_data_t *d, posix_errno_t *error) {
470     efile_win_t *w = (efile_win_t*)d;
471     HANDLE handle;
472 
473     ASSERT(enif_thread_type() == ERL_NIF_THR_DIRTY_IO_SCHEDULER);
474     ASSERT(erts_atomic32_read_nob(&d->state) == EFILE_STATE_CLOSED);
475     ASSERT(w->handle != INVALID_HANDLE_VALUE);
476 
477     handle = w->handle;
478     w->handle = INVALID_HANDLE_VALUE;
479 
480     enif_release_resource(d);
481 
482     if(!CloseHandle(handle)) {
483         *error = windows_to_posix_errno(GetLastError());
484         return 0;
485     }
486 
487     return 1;
488 }
489 
shift_overlapped(OVERLAPPED * overlapped,DWORD shift)490 static void shift_overlapped(OVERLAPPED *overlapped, DWORD shift) {
491     LARGE_INTEGER offset;
492 
493     ASSERT(shift >= 0);
494 
495     offset.HighPart = overlapped->OffsetHigh;
496     offset.LowPart = overlapped->Offset;
497 
498     /* ~(Uint64)0 is a magic value ("append to end of file") which needs to be
499      * preserved. Other positions resulting in overflow would have errored out
500      * just prior to this point. */
501     if(offset.QuadPart != ERTS_UINT64_MAX) {
502         offset.QuadPart += shift;
503     }
504 
505     /* All unused fields must be zeroed for the next call. */
506     sys_memset(overlapped, 0, sizeof(*overlapped));
507     overlapped->OffsetHigh = offset.HighPart;
508     overlapped->Offset = offset.LowPart;
509 }
510 
shift_iov(SysIOVec ** iov,int * iovlen,DWORD shift)511 static void shift_iov(SysIOVec **iov, int *iovlen, DWORD shift) {
512     SysIOVec *head_vec = (*iov);
513 
514     ASSERT(shift >= 0);
515 
516     while(shift > 0) {
517         ASSERT(head_vec < &(*iov)[*iovlen]);
518 
519         if(shift < head_vec->iov_len) {
520             head_vec->iov_base = (char*)head_vec->iov_base + shift;
521             head_vec->iov_len -= shift;
522             break;
523         } else {
524             shift -= head_vec->iov_len;
525             head_vec++;
526         }
527     }
528 
529     (*iovlen) -= head_vec - (*iov);
530     (*iov) = head_vec;
531 }
532 
533 typedef BOOL (WINAPI *io_op_t)(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED);
534 
internal_sync_io(efile_win_t * w,io_op_t operation,SysIOVec * iov,int iovlen,OVERLAPPED * overlapped)535 static Sint64 internal_sync_io(efile_win_t *w, io_op_t operation,
536         SysIOVec *iov, int iovlen, OVERLAPPED *overlapped) {
537 
538     Sint64 bytes_processed = 0;
539 
540     for(;;) {
541         DWORD block_bytes_processed, last_error;
542         BOOL succeeded;
543 
544         if(iovlen < 1) {
545             return bytes_processed;
546         }
547 
548         succeeded = operation(w->handle, iov->iov_base, iov->iov_len,
549             &block_bytes_processed, overlapped);
550         last_error = GetLastError();
551 
552         if(!succeeded && (last_error != ERROR_HANDLE_EOF)) {
553             w->common.posix_errno = windows_to_posix_errno(last_error);
554             return -1;
555         } else if(block_bytes_processed == 0) {
556             /* EOF */
557             return bytes_processed;
558         }
559 
560         if(overlapped != NULL) {
561             shift_overlapped(overlapped, block_bytes_processed);
562         }
563 
564         shift_iov(&iov, &iovlen, block_bytes_processed);
565 
566         bytes_processed += block_bytes_processed;
567     }
568 }
569 
efile_readv(efile_data_t * d,SysIOVec * iov,int iovlen)570 Sint64 efile_readv(efile_data_t *d, SysIOVec *iov, int iovlen) {
571     efile_win_t *w = (efile_win_t*)d;
572 
573     return internal_sync_io(w, ReadFile, iov, iovlen, NULL);
574 }
575 
efile_writev(efile_data_t * d,SysIOVec * iov,int iovlen)576 Sint64 efile_writev(efile_data_t *d, SysIOVec *iov, int iovlen) {
577     efile_win_t *w = (efile_win_t*)d;
578 
579     OVERLAPPED __overlapped, *overlapped;
580     Uint64 bytes_written;
581 
582     if(w->common.modes & EFILE_MODE_APPEND) {
583         overlapped = &__overlapped;
584 
585         sys_memset(overlapped, 0, sizeof(*overlapped));
586         overlapped->OffsetHigh = 0xFFFFFFFF;
587         overlapped->Offset = 0xFFFFFFFF;
588     } else {
589         overlapped = NULL;
590     }
591 
592     return internal_sync_io(w, WriteFile, iov, iovlen, overlapped);
593 }
594 
efile_preadv(efile_data_t * d,Sint64 offset,SysIOVec * iov,int iovlen)595 Sint64 efile_preadv(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen) {
596     efile_win_t *w = (efile_win_t*)d;
597 
598     OVERLAPPED overlapped;
599 
600     sys_memset(&overlapped, 0, sizeof(overlapped));
601     overlapped.OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
602     overlapped.Offset = offset & 0xFFFFFFFF;
603 
604     return internal_sync_io(w, ReadFile, iov, iovlen, &overlapped);
605 }
606 
efile_pwritev(efile_data_t * d,Sint64 offset,SysIOVec * iov,int iovlen)607 Sint64 efile_pwritev(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen) {
608     efile_win_t *w = (efile_win_t*)d;
609 
610     OVERLAPPED overlapped;
611 
612     sys_memset(&overlapped, 0, sizeof(overlapped));
613     overlapped.OffsetHigh = (offset >> 32) & 0xFFFFFFFF;
614     overlapped.Offset = offset & 0xFFFFFFFF;
615 
616     return internal_sync_io(w, WriteFile, iov, iovlen, &overlapped);
617 }
618 
efile_seek(efile_data_t * d,enum efile_seek_t seek,Sint64 offset,Sint64 * new_position)619 int efile_seek(efile_data_t *d, enum efile_seek_t seek, Sint64 offset, Sint64 *new_position) {
620     efile_win_t *w = (efile_win_t*)d;
621 
622     LARGE_INTEGER large_offset, large_new_position;
623     DWORD whence;
624 
625     switch(seek) {
626         case EFILE_SEEK_BOF: whence = FILE_BEGIN; break;
627         case EFILE_SEEK_CUR: whence = FILE_CURRENT; break;
628         case EFILE_SEEK_EOF: whence = FILE_END; break;
629         default: ERTS_INTERNAL_ERROR("Invalid seek parameter");
630     }
631 
632     large_offset.QuadPart = offset;
633 
634     if(!SetFilePointerEx(w->handle, large_offset, &large_new_position, whence)) {
635         w->common.posix_errno = windows_to_posix_errno(GetLastError());
636         return 0;
637     }
638 
639     (*new_position) = large_new_position.QuadPart;
640 
641     return 1;
642 }
643 
efile_sync(efile_data_t * d,int data_only)644 int efile_sync(efile_data_t *d, int data_only) {
645     efile_win_t *w = (efile_win_t*)d;
646 
647     /* Windows doesn't support data-only syncing. */
648     (void)data_only;
649 
650     if(!FlushFileBuffers(w->handle)) {
651         w->common.posix_errno = windows_to_posix_errno(GetLastError());
652         return 0;
653     }
654 
655     return 1;
656 }
657 
efile_advise(efile_data_t * d,Sint64 offset,Sint64 length,enum efile_advise_t advise)658 int efile_advise(efile_data_t *d, Sint64 offset, Sint64 length, enum efile_advise_t advise) {
659     /* Windows doesn't support this, but we'll pretend it does since the call
660      * is only a recommendation even on systems that do support it. */
661 
662     (void)d;
663     (void)offset;
664     (void)length;
665     (void)advise;
666 
667     return 1;
668 }
669 
efile_allocate(efile_data_t * d,Sint64 offset,Sint64 length)670 int efile_allocate(efile_data_t *d, Sint64 offset, Sint64 length) {
671     efile_win_t *w = (efile_win_t*)d;
672 
673     (void)d;
674     (void)offset;
675     (void)length;
676 
677     w->common.posix_errno = ENOTSUP;
678 
679     return 0;
680 }
681 
efile_truncate(efile_data_t * d)682 int efile_truncate(efile_data_t *d) {
683     efile_win_t *w = (efile_win_t*)d;
684 
685     if(!SetEndOfFile(w->handle)) {
686         w->common.posix_errno = windows_to_posix_errno(GetLastError());
687         return 0;
688     }
689 
690     return 1;
691 }
692 
is_executable_file(const efile_path_t * path)693 static int is_executable_file(const efile_path_t *path) {
694     /* We're using the file extension in order to be quirks-compliant with the
695      * old driver, which never bothered to check the actual permissions. We
696      * could easily do so now (cf. GetNamedSecurityInfo) but the execute
697      * permission is only relevant for files that are started with the default
698      * loader, and batch files run just fine with read permission alone. */
699 
700     int length = PATH_LENGTH(path);
701 
702     if(length >= 4) {
703         const WCHAR *last_four = &((WCHAR*)path->data)[length - 4];
704 
705         if (!_wcsicmp(last_four, L".exe") ||
706             !_wcsicmp(last_four, L".cmd") ||
707             !_wcsicmp(last_four, L".bat") ||
708             !_wcsicmp(last_four, L".com")) {
709             return 1;
710         }
711     }
712 
713     return 0;
714 }
715 
716 /* Returns whether the path refers to a link-like object, e.g. a junction
717  * point, symbolic link, or mounted folder. */
is_name_surrogate(const efile_path_t * path)718 static int is_name_surrogate(const efile_path_t *path) {
719     HANDLE handle;
720     int result;
721 
722     handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ,
723                          FILE_SHARE_FLAGS, NULL, OPEN_EXISTING,
724                          FILE_FLAG_OPEN_REPARSE_POINT |
725                          FILE_FLAG_BACKUP_SEMANTICS,
726                          NULL);
727     result = 0;
728 
729     if(handle != INVALID_HANDLE_VALUE) {
730         REPARSE_GUID_DATA_BUFFER reparse_buffer;
731         DWORD unused_length;
732         BOOL success;
733 
734         success = DeviceIoControl(handle,
735                                   FSCTL_GET_REPARSE_POINT, NULL, 0,
736                                   &reparse_buffer, sizeof(reparse_buffer),
737                                   &unused_length, NULL);
738 
739         /* ERROR_MORE_DATA is tolerated since we're guaranteed to have filled
740          * the field we want. */
741         if(success || GetLastError() == ERROR_MORE_DATA) {
742             result = IsReparseTagNameSurrogate(reparse_buffer.ReparseTag);
743         }
744 
745         CloseHandle(handle);
746      }
747 
748      return result;
749 }
750 
efile_read_info(const efile_path_t * path,int follow_links,efile_fileinfo_t * result)751 posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) {
752     BY_HANDLE_FILE_INFORMATION native_file_info;
753     DWORD attributes;
754     int is_link;
755 
756     sys_memset(&native_file_info, 0, sizeof(native_file_info));
757     is_link = 0;
758 
759     attributes = GetFileAttributesW((WCHAR*)path->data);
760 
761     if(attributes == INVALID_FILE_ATTRIBUTES) {
762         DWORD last_error = GetLastError();
763 
764         /* Querying a network share root fails with ERROR_BAD_NETPATH, so we'll
765          * fake it as a directory just like local roots. */
766         if(!is_path_root(path) || last_error != ERROR_BAD_NETPATH) {
767             return windows_to_posix_errno(last_error);
768         }
769 
770         attributes = FILE_ATTRIBUTE_DIRECTORY;
771     } else if(is_path_root(path)) {
772         /* Local (or mounted) roots can be queried with GetFileAttributesW but
773          * lack support for GetFileInformationByHandle, so we'll skip that
774          * part. */
775     } else {
776         HANDLE handle;
777 
778         if(attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
779             is_link = is_name_surrogate(path);
780         }
781 
782         if(follow_links && is_link) {
783             posix_errno_t posix_errno;
784             efile_path_t resolved_path;
785 
786             posix_errno = internal_read_link(path, &resolved_path);
787 
788             if(posix_errno != 0) {
789                 return posix_errno;
790             }
791 
792             return efile_read_info(&resolved_path, 0, result);
793         }
794 
795         handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ,
796             FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
797             NULL);
798 
799         /* The old driver never cared whether this succeeded. */
800         if(handle != INVALID_HANDLE_VALUE) {
801             GetFileInformationByHandle(handle, &native_file_info);
802             CloseHandle(handle);
803         }
804 
805         FILETIME_TO_EPOCH(result->m_time, native_file_info.ftLastWriteTime);
806         FILETIME_TO_EPOCH(result->a_time, native_file_info.ftLastAccessTime);
807         FILETIME_TO_EPOCH(result->c_time, native_file_info.ftCreationTime);
808 
809         if(result->m_time == -EPOCH_DIFFERENCE) {
810             /* Default to 1970 just like the old driver. */
811             result->m_time = 0;
812         }
813 
814         if(result->a_time == -EPOCH_DIFFERENCE) {
815             result->a_time = result->m_time;
816         }
817 
818         if(result->c_time == -EPOCH_DIFFERENCE) {
819             result->c_time = result->m_time;
820         }
821     }
822 
823     if(is_link) {
824         result->type = EFILE_FILETYPE_SYMLINK;
825         /* This should be _S_IFLNK, but the old driver always set
826          * non-directories to _S_IFREG. */
827         result->mode |= _S_IFREG;
828     } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) {
829         result->type = EFILE_FILETYPE_DIRECTORY;
830         result->mode |= _S_IFDIR | _S_IEXEC;
831     } else {
832         if(is_executable_file(path)) {
833             result->mode |= _S_IEXEC;
834         }
835 
836         result->type = EFILE_FILETYPE_REGULAR;
837         result->mode |= _S_IFREG;
838     }
839 
840     if(!(attributes & FILE_ATTRIBUTE_READONLY)) {
841         result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE;
842         result->mode |= _S_IREAD | _S_IWRITE;
843     } else {
844         result->access = EFILE_ACCESS_READ;
845         result->mode |= _S_IREAD;
846     }
847 
848     /* Propagate user mode-bits to group/other fields */
849     result->mode |= (result->mode & 0700) >> 3;
850     result->mode |= (result->mode & 0700) >> 6;
851 
852     result->size =
853         ((Uint64)native_file_info.nFileSizeHigh << 32ull) |
854         (Uint64)native_file_info.nFileSizeLow;
855 
856     result->links = MAX(1, native_file_info.nNumberOfLinks);
857 
858     result->major_device = get_drive_number(path);
859     result->minor_device = 0;
860     result->inode = 0;
861     result->uid = 0;
862     result->gid = 0;
863 
864     return 0;
865 }
866 
efile_set_permissions(const efile_path_t * path,Uint32 permissions)867 posix_errno_t efile_set_permissions(const efile_path_t *path, Uint32 permissions) {
868     DWORD attributes = GetFileAttributesW((WCHAR*)path->data);
869 
870     if(attributes == INVALID_FILE_ATTRIBUTES) {
871         return windows_to_posix_errno(GetLastError());
872     }
873 
874     if(permissions & _S_IWRITE) {
875         attributes &= ~FILE_ATTRIBUTE_READONLY;
876     } else {
877         attributes |= FILE_ATTRIBUTE_READONLY;
878     }
879 
880     if(SetFileAttributesW((WCHAR*)path->data, attributes)) {
881         return 0;
882     }
883 
884     return windows_to_posix_errno(GetLastError());
885 }
886 
efile_set_owner(const efile_path_t * path,Sint32 owner,Sint32 group)887 posix_errno_t efile_set_owner(const efile_path_t *path, Sint32 owner, Sint32 group) {
888     (void)path;
889     (void)owner;
890     (void)group;
891 
892     return 0;
893 }
894 
efile_set_time(const efile_path_t * path,Sint64 a_time,Sint64 m_time,Sint64 c_time)895 posix_errno_t efile_set_time(const efile_path_t *path, Sint64 a_time, Sint64 m_time, Sint64 c_time) {
896     FILETIME accessed, modified, created;
897     DWORD last_error, attributes;
898     HANDLE handle;
899 
900     attributes = GetFileAttributesW((WCHAR*)path->data);
901 
902     if(attributes == INVALID_FILE_ATTRIBUTES) {
903         return windows_to_posix_errno(GetLastError());
904     }
905 
906     /* If the file is read-only, we have to make it temporarily writable while
907      * setting new metadata. */
908     if(attributes & FILE_ATTRIBUTE_READONLY) {
909         DWORD without_readonly = attributes & ~FILE_ATTRIBUTE_READONLY;
910 
911         if(!SetFileAttributesW((WCHAR*)path->data, without_readonly)) {
912             return windows_to_posix_errno(GetLastError());
913         }
914     }
915 
916     EPOCH_TO_FILETIME(modified, m_time);
917     EPOCH_TO_FILETIME(accessed, a_time);
918     EPOCH_TO_FILETIME(created, c_time);
919 
920     handle = CreateFileW((WCHAR*)path->data, GENERIC_READ | GENERIC_WRITE,
921         FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
922     last_error = GetLastError();
923 
924     if(handle != INVALID_HANDLE_VALUE) {
925         if(SetFileTime(handle, &created, &accessed, &modified)) {
926             last_error = ERROR_SUCCESS;
927         } else {
928             last_error = GetLastError();
929         }
930 
931         CloseHandle(handle);
932     }
933 
934     if(attributes & FILE_ATTRIBUTE_READONLY) {
935         SetFileAttributesW((WCHAR*)path->data, attributes);
936     }
937 
938     return windows_to_posix_errno(last_error);
939 }
940 
internal_read_link(const efile_path_t * path,efile_path_t * result)941 static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *result) {
942     DWORD required_length, actual_length;
943     HANDLE link_handle;
944     DWORD last_error;
945 
946     link_handle = CreateFileW((WCHAR*)path->data, GENERIC_READ,
947         FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
948     last_error = GetLastError();
949 
950     if(link_handle == INVALID_HANDLE_VALUE) {
951         return windows_to_posix_errno(last_error);
952     }
953 
954     required_length = GetFinalPathNameByHandleW(link_handle, NULL, 0, 0);
955     last_error = GetLastError();
956 
957     if(required_length <= 0) {
958         CloseHandle(link_handle);
959         return windows_to_posix_errno(last_error);
960     }
961 
962     /* Unlike many other path functions (eg. GetFullPathNameW), this one
963      * includes the NUL terminator in its required length. */
964     if(!enif_alloc_binary(required_length * sizeof(WCHAR), result)) {
965         CloseHandle(link_handle);
966         return ENOMEM;
967     }
968 
969     actual_length = GetFinalPathNameByHandleW(link_handle,
970         (WCHAR*)result->data, required_length, 0);
971     last_error = GetLastError();
972 
973     CloseHandle(link_handle);
974 
975     if(actual_length == 0 || actual_length >= required_length) {
976         enif_release_binary(result);
977         return windows_to_posix_errno(last_error);
978     }
979 
980     /* GetFinalPathNameByHandle always prepends with "\\?\" and NUL-terminates,
981      * so we never have to touch-up the resulting path. */
982 
983     ASSERT_PATH_FORMAT(result);
984 
985     return 0;
986 }
987 
efile_read_link(ErlNifEnv * env,const efile_path_t * path,ERL_NIF_TERM * result)988 posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
989     posix_errno_t posix_errno;
990     ErlNifBinary result_bin;
991     DWORD attributes;
992 
993     ASSERT_PATH_FORMAT(path);
994 
995     attributes = GetFileAttributesW((WCHAR*)path->data);
996 
997     if(attributes == INVALID_FILE_ATTRIBUTES) {
998         return windows_to_posix_errno(GetLastError());
999     } else if(!(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
1000         return EINVAL;
1001     }
1002 
1003     if(!is_name_surrogate(path)) {
1004         return EINVAL;
1005     }
1006 
1007     posix_errno = internal_read_link(path, &result_bin);
1008 
1009     if(posix_errno == 0) {
1010         if(!normalize_path_result(&result_bin)) {
1011             enif_release_binary(&result_bin);
1012             return ENOMEM;
1013         }
1014 
1015         (*result) = enif_make_binary(env, &result_bin);
1016     }
1017 
1018     return posix_errno;
1019 }
1020 
efile_list_dir(ErlNifEnv * env,const efile_path_t * path,ERL_NIF_TERM * result)1021 posix_errno_t efile_list_dir(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
1022     ERL_NIF_TERM list_head;
1023     WIN32_FIND_DATAW data;
1024     HANDLE search_handle;
1025     WCHAR *search_path;
1026     DWORD last_error;
1027 
1028     ASSERT_PATH_FORMAT(path);
1029 
1030     search_path = enif_alloc(path->size + 2 * sizeof(WCHAR));
1031 
1032     if(search_path == NULL) {
1033         return ENOMEM;
1034     }
1035 
1036     sys_memcpy(search_path, path->data, path->size);
1037     search_path[PATH_LENGTH(path) + 0] = L'\\';
1038     search_path[PATH_LENGTH(path) + 1] = L'*';
1039     search_path[PATH_LENGTH(path) + 2] = L'\0';
1040 
1041     search_handle = FindFirstFileW(search_path, &data);
1042     last_error = GetLastError();
1043 
1044     enif_free(search_path);
1045 
1046     if(search_handle == INVALID_HANDLE_VALUE) {
1047         return windows_to_posix_errno(last_error);
1048     }
1049 
1050     list_head = enif_make_list(env, 0);
1051 
1052     do {
1053         int name_length = wcslen(data.cFileName);
1054 
1055         if(!is_ignored_name(name_length, data.cFileName)) {
1056             unsigned char *name_bytes;
1057             ERL_NIF_TERM name_term;
1058             size_t name_size;
1059 
1060             name_size = name_length * sizeof(WCHAR);
1061 
1062             name_bytes = enif_make_new_binary(env, name_size, &name_term);
1063             sys_memcpy(name_bytes, data.cFileName, name_size);
1064 
1065             list_head = enif_make_list_cell(env, name_term, list_head);
1066         }
1067     } while(FindNextFileW(search_handle, &data));
1068 
1069     FindClose(search_handle);
1070     (*result) = list_head;
1071 
1072     return 0;
1073 }
1074 
efile_rename(const efile_path_t * old_path,const efile_path_t * new_path)1075 posix_errno_t efile_rename(const efile_path_t *old_path, const efile_path_t *new_path) {
1076     BOOL old_is_directory, new_is_directory;
1077     DWORD move_flags, last_error;
1078 
1079     ASSERT_PATH_FORMAT(old_path);
1080     ASSERT_PATH_FORMAT(new_path);
1081 
1082     move_flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH;
1083 
1084     if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) {
1085         return 0;
1086     }
1087 
1088     last_error = GetLastError();
1089 
1090     old_is_directory = has_file_attributes(old_path, FILE_ATTRIBUTE_DIRECTORY);
1091     new_is_directory = has_file_attributes(new_path, FILE_ATTRIBUTE_DIRECTORY);
1092 
1093     switch(last_error) {
1094     case ERROR_SHARING_VIOLATION:
1095     case ERROR_ACCESS_DENIED:
1096         if(old_is_directory) {
1097             BOOL moved_into_itself;
1098 
1099             moved_into_itself = (old_path->size <= new_path->size) &&
1100                 !_wcsnicmp((WCHAR*)old_path->data, (WCHAR*)new_path->data,
1101                     PATH_LENGTH(old_path));
1102 
1103             if(moved_into_itself) {
1104                 return EINVAL;
1105             } else if(is_path_root(old_path)) {
1106                 return EINVAL;
1107             }
1108 
1109             /* Renaming a directory across volumes needs to be rewritten as
1110              * EXDEV so that the caller can respond by simulating it with
1111              * copy/delete operations.
1112              *
1113              * Files are handled through MOVEFILE_COPY_ALLOWED. */
1114             if(!has_same_mount_point(old_path, new_path)) {
1115                 return EXDEV;
1116             }
1117         }
1118         break;
1119     case ERROR_PATH_NOT_FOUND:
1120     case ERROR_FILE_NOT_FOUND:
1121         return ENOENT;
1122     case ERROR_ALREADY_EXISTS:
1123     case ERROR_FILE_EXISTS:
1124         if(old_is_directory && !new_is_directory) {
1125             return ENOTDIR;
1126         } else if(!old_is_directory && new_is_directory) {
1127             return EISDIR;
1128         } else if(old_is_directory && new_is_directory) {
1129             /* This will fail if the destination isn't empty. */
1130             if(RemoveDirectoryW((WCHAR*)new_path->data)) {
1131                 return efile_rename(old_path, new_path);
1132             }
1133 
1134             return EEXIST;
1135         } else if(!old_is_directory && !new_is_directory) {
1136             /* This is pretty iffy; the public documentation says that the
1137              * operation may EACCES on some systems when either file is open,
1138              * which gives us room to use MOVEFILE_REPLACE_EXISTING and be done
1139              * with it, but the old implementation simulated Unix semantics and
1140              * there's a lot of code that relies on that.
1141              *
1142              * The simulation renames the destination to a scratch name to get
1143              * around the fact that it's impossible to open (and by extension
1144              * rename) a file that's been deleted while open. It has a few
1145              * drawbacks though;
1146              *
1147              * 1) It's not atomic as there's a small window where there's no
1148              *    file at all on the destination path.
1149              * 2) It will confuse applications that subscribe to folder
1150              *    changes.
1151              * 3) It will fail if we lack general permission to write in the
1152              *    same folder. */
1153 
1154             WCHAR *swap_path = enif_alloc(new_path->size + sizeof(WCHAR) * 64);
1155 
1156             if(swap_path == NULL) {
1157                 return ENOMEM;
1158             } else {
1159                 static LONGLONG unique_counter = 0;
1160                 WCHAR *swap_path_end;
1161 
1162                 /* We swap in the same folder as the destination to be
1163                  * reasonably sure that it's on the same volume. Note that
1164                  * we're avoiding GetTempFileNameW as it will fail on long
1165                  * paths. */
1166 
1167                 sys_memcpy(swap_path, (WCHAR*)new_path->data, new_path->size);
1168                 swap_path_end = swap_path + PATH_LENGTH(new_path);
1169 
1170                 while(!IS_SLASH(*swap_path_end)) {
1171                     ASSERT(swap_path_end > swap_path);
1172                     swap_path_end--;
1173                 }
1174 
1175                 StringCchPrintfW(&swap_path_end[1], 64, L"erl-%lx-%llx.tmp",
1176                     GetCurrentProcessId(), unique_counter);
1177                 InterlockedIncrement64(&unique_counter);
1178             }
1179 
1180             if(MoveFileExW((WCHAR*)new_path->data, swap_path, MOVEFILE_REPLACE_EXISTING)) {
1181                 if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) {
1182                     last_error = ERROR_SUCCESS;
1183                     DeleteFileW(swap_path);
1184                 } else {
1185                     last_error = GetLastError();
1186                     MoveFileW(swap_path, (WCHAR*)new_path->data);
1187                 }
1188             } else {
1189                 last_error = GetLastError();
1190                 DeleteFileW(swap_path);
1191             }
1192 
1193             enif_free(swap_path);
1194 
1195             return windows_to_posix_errno(last_error);
1196         }
1197 
1198         return EEXIST;
1199     }
1200 
1201     return windows_to_posix_errno(last_error);
1202 }
1203 
efile_make_hard_link(const efile_path_t * existing_path,const efile_path_t * new_path)1204 posix_errno_t efile_make_hard_link(const efile_path_t *existing_path, const efile_path_t *new_path) {
1205     ASSERT_PATH_FORMAT(existing_path);
1206     ASSERT_PATH_FORMAT(new_path);
1207 
1208     if(!CreateHardLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, NULL)) {
1209         return windows_to_posix_errno(GetLastError());
1210     }
1211 
1212     return 0;
1213 }
1214 
efile_make_soft_link(const efile_path_t * existing_path,const efile_path_t * new_path)1215 posix_errno_t efile_make_soft_link(const efile_path_t *existing_path, const efile_path_t *new_path) {
1216     DWORD link_flags;
1217 
1218     ASSERT_PATH_FORMAT(existing_path);
1219     ASSERT_PATH_FORMAT(new_path);
1220 
1221     if(has_file_attributes(existing_path, FILE_ATTRIBUTE_DIRECTORY)) {
1222         link_flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
1223     } else {
1224         link_flags = 0;
1225     }
1226 
1227     if(!CreateSymbolicLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, link_flags)) {
1228         return windows_to_posix_errno(GetLastError());
1229     }
1230 
1231     return 0;
1232 }
1233 
efile_make_dir(const efile_path_t * path)1234 posix_errno_t efile_make_dir(const efile_path_t *path) {
1235     ASSERT_PATH_FORMAT(path);
1236 
1237     if(!CreateDirectoryW((WCHAR*)path->data, NULL)) {
1238         return windows_to_posix_errno(GetLastError());
1239     }
1240 
1241     return 0;
1242 }
1243 
efile_del_file(const efile_path_t * path)1244 posix_errno_t efile_del_file(const efile_path_t *path) {
1245     ASSERT_PATH_FORMAT(path);
1246 
1247     if(!DeleteFileW((WCHAR*)path->data)) {
1248         DWORD last_error = GetLastError();
1249 
1250         switch(last_error) {
1251         case ERROR_INVALID_NAME:
1252             /* Attempted to delete a device or similar. */
1253             return EACCES;
1254         case ERROR_ACCESS_DENIED:
1255             /* Windows NT reports removing a directory as EACCES instead of
1256              * EPERM. */
1257             if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
1258                 return EPERM;
1259             }
1260             break;
1261         }
1262 
1263         return windows_to_posix_errno(last_error);
1264     }
1265 
1266     return 0;
1267 }
1268 
efile_del_dir(const efile_path_t * path)1269 posix_errno_t efile_del_dir(const efile_path_t *path) {
1270     ASSERT_PATH_FORMAT(path);
1271 
1272     if(!RemoveDirectoryW((WCHAR*)path->data)) {
1273         DWORD last_error = GetLastError();
1274 
1275         if(last_error == ERROR_DIRECTORY) {
1276             return ENOTDIR;
1277         }
1278 
1279         return windows_to_posix_errno(last_error);
1280     }
1281 
1282     return 0;
1283 }
1284 
efile_set_cwd(const efile_path_t * path)1285 posix_errno_t efile_set_cwd(const efile_path_t *path) {
1286     const WCHAR *path_start;
1287 
1288     ASSERT_PATH_FORMAT(path);
1289 
1290     /* We have to use _wchdir since that's the only function that updates the
1291      * per-drive working directory, but it naively assumes that all paths
1292      * starting with \\ are UNC paths, so we have to skip the long-path prefix.
1293      *
1294      * _wchdir doesn't handle long-prefixed UNC paths either so we hand those
1295      * to SetCurrentDirectoryW instead. The per-drive working directory is
1296      * irrelevant for such paths anyway. */
1297 
1298     if(!IS_LONG_UNC_PATH(PATH_LENGTH(path), path->data)) {
1299         path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;
1300 
1301         if(_wchdir(path_start)) {
1302             return windows_to_posix_errno(GetLastError());
1303         }
1304     } else {
1305         if(!SetCurrentDirectoryW((WCHAR*)path->data)) {
1306             return windows_to_posix_errno(GetLastError());
1307         }
1308     }
1309 
1310     return 0;
1311 }
1312 
is_valid_drive(int device_index)1313 static int is_valid_drive(int device_index) {
1314     WCHAR drive_path[4] = {L'?', L':', L'\\', L'\0'};
1315 
1316     if(device_index == 0) {
1317         /* Default drive; always valid. */
1318         return 1;
1319     } else if(device_index > (L'Z' - L'A' + 1)) {
1320         return 0;
1321     }
1322 
1323     drive_path[0] = device_index + L'A' - 1;
1324 
1325     switch(GetDriveTypeW(drive_path)) {
1326     case DRIVE_NO_ROOT_DIR:
1327     case DRIVE_UNKNOWN:
1328         return 0;
1329     }
1330 
1331     return 1;
1332 }
1333 
efile_get_device_cwd(ErlNifEnv * env,int device_index,ERL_NIF_TERM * result)1334 posix_errno_t efile_get_device_cwd(ErlNifEnv *env, int device_index, ERL_NIF_TERM *result) {
1335     ErlNifBinary result_bin;
1336 
1337     /* _wgetdcwd might crash the entire emulator on debug builds since the CRT
1338      * invalid parameter handler asserts if passed a non-existent drive (Or
1339      * simply one that has been unmounted), so we check it ourselves to avoid
1340      * that. */
1341     if(!is_valid_drive(device_index)) {
1342         return EACCES;
1343     }
1344 
1345     if(!enif_alloc_binary(MAX_PATH * sizeof(WCHAR), &result_bin)) {
1346         return ENOMEM;
1347     }
1348 
1349     if(_wgetdcwd(device_index, (WCHAR*)result_bin.data, MAX_PATH) == NULL) {
1350         enif_release_binary(&result_bin);
1351         return EACCES;
1352     }
1353 
1354     if(!normalize_path_result(&result_bin)) {
1355         enif_release_binary(&result_bin);
1356         return ENOMEM;
1357     }
1358 
1359     (*result) = enif_make_binary(env, &result_bin);
1360 
1361     return 0;
1362 }
1363 
efile_get_cwd(ErlNifEnv * env,ERL_NIF_TERM * result)1364 posix_errno_t efile_get_cwd(ErlNifEnv *env, ERL_NIF_TERM *result) {
1365     return efile_get_device_cwd(env, 0, result);
1366 }
1367 
efile_altname(ErlNifEnv * env,const efile_path_t * path,ERL_NIF_TERM * result)1368 posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
1369     ErlNifBinary result_bin;
1370 
1371     ASSERT_PATH_FORMAT(path);
1372 
1373     if(is_path_root(path)) {
1374         /* Root paths can't be queried so we'll just return them as they are. */
1375         if(!enif_alloc_binary(path->size, &result_bin)) {
1376             return ENOMEM;
1377         }
1378 
1379         sys_memcpy(result_bin.data, path->data, path->size);
1380     } else {
1381         WIN32_FIND_DATAW data;
1382         HANDLE handle;
1383 
1384         WCHAR *name_buffer;
1385         int name_length;
1386 
1387         /* Reject path wildcards. */
1388         if(wcspbrk(&((const WCHAR*)path->data)[LP_PREFIX_LENGTH], L"?*")) {
1389             return ENOENT;
1390         }
1391 
1392         handle = FindFirstFileW((const WCHAR*)path->data, &data);
1393 
1394         if(handle == INVALID_HANDLE_VALUE) {
1395             return windows_to_posix_errno(GetLastError());
1396         }
1397 
1398         FindClose(handle);
1399 
1400         name_length = wcslen(data.cAlternateFileName);
1401 
1402         if(name_length > 0) {
1403             name_buffer = data.cAlternateFileName;
1404         } else {
1405             name_length = wcslen(data.cFileName);
1406             name_buffer = data.cFileName;
1407         }
1408 
1409         /* Include NUL-terminator; it will be removed after normalization. */
1410         name_length += 1;
1411 
1412         if(!enif_alloc_binary(name_length * sizeof(WCHAR), &result_bin)) {
1413             return ENOMEM;
1414         }
1415 
1416         sys_memcpy(result_bin.data, name_buffer, name_length * sizeof(WCHAR));
1417     }
1418 
1419     if(!normalize_path_result(&result_bin)) {
1420         enif_release_binary(&result_bin);
1421         return ENOMEM;
1422     }
1423 
1424     (*result) = enif_make_binary(env, &result_bin);
1425 
1426     return 0;
1427 }
1428 
windows_to_posix_errno(DWORD last_error)1429 static int windows_to_posix_errno(DWORD last_error) {
1430     switch(last_error) {
1431     case ERROR_SUCCESS:
1432         return 0;
1433     case ERROR_INVALID_FUNCTION:
1434     case ERROR_INVALID_DATA:
1435     case ERROR_INVALID_PARAMETER:
1436     case ERROR_INVALID_TARGET_HANDLE:
1437     case ERROR_INVALID_CATEGORY:
1438     case ERROR_NEGATIVE_SEEK:
1439         return EINVAL;
1440     case ERROR_DIR_NOT_EMPTY:
1441         return EEXIST;
1442     case ERROR_BAD_FORMAT:
1443         return ENOEXEC;
1444     case ERROR_PATH_NOT_FOUND:
1445     case ERROR_FILE_NOT_FOUND:
1446     case ERROR_NO_MORE_FILES:
1447     case ERROR_INVALID_NAME:
1448         return ENOENT;
1449     case ERROR_TOO_MANY_OPEN_FILES:
1450         return EMFILE;
1451     case ERROR_ACCESS_DENIED:
1452     case ERROR_INVALID_ACCESS:
1453     case ERROR_CURRENT_DIRECTORY:
1454     case ERROR_SHARING_VIOLATION:
1455     case ERROR_LOCK_VIOLATION:
1456     case ERROR_INVALID_PASSWORD:
1457     case ERROR_DRIVE_LOCKED:
1458         return EACCES;
1459     case ERROR_INVALID_HANDLE:
1460         return EBADF;
1461     case ERROR_NOT_ENOUGH_MEMORY:
1462     case ERROR_OUTOFMEMORY:
1463     case ERROR_OUT_OF_STRUCTURES:
1464         return ENOMEM;
1465     case ERROR_INVALID_DRIVE:
1466     case ERROR_BAD_UNIT:
1467     case ERROR_NOT_READY:
1468     case ERROR_REM_NOT_LIST:
1469     case ERROR_DUP_NAME:
1470     case ERROR_BAD_NETPATH:
1471     case ERROR_NETWORK_BUSY:
1472     case ERROR_DEV_NOT_EXIST:
1473     case ERROR_BAD_NET_NAME:
1474         return ENXIO;
1475     case ERROR_NOT_SAME_DEVICE:
1476         return EXDEV;
1477     case ERROR_WRITE_PROTECT:
1478         return EROFS;
1479     case ERROR_BAD_LENGTH:
1480     case ERROR_BUFFER_OVERFLOW:
1481         return E2BIG;
1482     case ERROR_SEEK:
1483     case ERROR_SECTOR_NOT_FOUND:
1484         return ESPIPE;
1485     case ERROR_NOT_DOS_DISK:
1486         return ENODEV;
1487     case ERROR_GEN_FAILURE:
1488         return ENODEV;
1489     case ERROR_SHARING_BUFFER_EXCEEDED:
1490     case ERROR_NO_MORE_SEARCH_HANDLES:
1491         return EMFILE;
1492     case ERROR_HANDLE_EOF:
1493     case ERROR_BROKEN_PIPE:
1494         return EPIPE;
1495     case ERROR_HANDLE_DISK_FULL:
1496     case ERROR_DISK_FULL:
1497         return ENOSPC;
1498     case ERROR_NOT_SUPPORTED:
1499         return ENOTSUP;
1500     case ERROR_FILE_EXISTS:
1501     case ERROR_ALREADY_EXISTS:
1502     case ERROR_CANNOT_MAKE:
1503         return EEXIST;
1504     case ERROR_ALREADY_ASSIGNED:
1505         return EBUSY;
1506     case ERROR_NO_PROC_SLOTS:
1507         return EAGAIN;
1508     case ERROR_CANT_RESOLVE_FILENAME:
1509         return EMLINK;
1510     case ERROR_PRIVILEGE_NOT_HELD:
1511         return EPERM;
1512     case ERROR_ARENA_TRASHED:
1513     case ERROR_INVALID_BLOCK:
1514     case ERROR_BAD_ENVIRONMENT:
1515     case ERROR_BAD_COMMAND:
1516     case ERROR_CRC:
1517     case ERROR_OUT_OF_PAPER:
1518     case ERROR_READ_FAULT:
1519     case ERROR_WRITE_FAULT:
1520     case ERROR_WRONG_DISK:
1521     case ERROR_NET_WRITE_FAULT:
1522         return EIO;
1523     default: /* not to do with files I expect. */
1524         return EIO;
1525     }
1526 }
1527