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