1/*
2 * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/* Access APIs for WinXP and above */
27#ifndef _WIN32_WINNT
28#define _WIN32_WINNT 0x0501
29#endif
30
31#include <assert.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <ctype.h>
35#include <direct.h>
36#include <windows.h>
37#include <io.h>
38
39#include "jni.h"
40#include "io_util.h"
41#include "jlong.h"
42#include "io_util_md.h"
43#include "dirent_md.h"
44#include "java_io_FileSystem.h"
45
46#define MAX_PATH_LENGTH 1024
47
48static struct {
49    jfieldID path;
50} ids;
51
52/**
53 * GetFinalPathNameByHandle is available on Windows Vista and newer
54 */
55typedef BOOL (WINAPI* GetFinalPathNameByHandleProc) (HANDLE, LPWSTR, DWORD, DWORD);
56static GetFinalPathNameByHandleProc GetFinalPathNameByHandle_func;
57
58JNIEXPORT void JNICALL
59Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls)
60{
61    HMODULE handle;
62    jclass fileClass;
63
64    fileClass = (*env)->FindClass(env, "java/io/File");
65    CHECK_NULL(fileClass);
66    ids.path = (*env)->GetFieldID(env, fileClass, "path", "Ljava/lang/String;");
67    CHECK_NULL(ids.path);
68
69    // GetFinalPathNameByHandle requires Windows Vista or newer
70    if (GetModuleHandleExW((GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
71                            GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT),
72                           (LPCWSTR)&CreateFileW, &handle) != 0)
73    {
74        GetFinalPathNameByHandle_func = (GetFinalPathNameByHandleProc)
75            GetProcAddress(handle, "GetFinalPathNameByHandleW");
76    }
77}
78
79/* -- Path operations -- */
80
81extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len);
82extern int wcanonicalizeWithPrefix(const WCHAR *canonicalPrefix, const WCHAR *pathWithCanonicalPrefix, WCHAR *out, int len);
83
84/**
85 * Retrieves the fully resolved (final) path for the given path or NULL
86 * if the function fails.
87 */
88static WCHAR* getFinalPath(JNIEnv *env, const WCHAR *path)
89{
90    HANDLE h;
91    WCHAR *result;
92    DWORD error;
93
94    /* Need Windows Vista or newer to get the final path */
95    if (GetFinalPathNameByHandle_func == NULL)
96        return NULL;
97
98    h = CreateFileW(path,
99                    FILE_READ_ATTRIBUTES,
100                    FILE_SHARE_DELETE |
101                        FILE_SHARE_READ | FILE_SHARE_WRITE,
102                    NULL,
103                    OPEN_EXISTING,
104                    FILE_FLAG_BACKUP_SEMANTICS,
105                    NULL);
106    if (h == INVALID_HANDLE_VALUE)
107        return NULL;
108
109    /**
110     * Allocate a buffer for the resolved path. For a long path we may need
111     * to allocate a larger buffer.
112     */
113    result = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR));
114    if (result != NULL) {
115        DWORD len = (*GetFinalPathNameByHandle_func)(h, result, MAX_PATH, 0);
116        if (len >= MAX_PATH) {
117            /* retry with a buffer of the right size */
118            WCHAR* newResult = (WCHAR*)realloc(result, (len+1) * sizeof(WCHAR));
119            if (newResult != NULL) {
120                result = newResult;
121                len = (*GetFinalPathNameByHandle_func)(h, result, len, 0);
122            } else {
123                len = 0;
124                JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
125            }
126        }
127
128        if (len > 0) {
129            /**
130             * Strip prefix (should be \\?\ or \\?\UNC)
131             */
132            if (result[0] == L'\\' && result[1] == L'\\' &&
133                result[2] == L'?' && result[3] == L'\\')
134            {
135                int isUnc = (result[4] == L'U' &&
136                             result[5] == L'N' &&
137                             result[6] == L'C');
138                int prefixLen = (isUnc) ? 7 : 4;
139                /* actual result length (includes terminator) */
140                int resultLen = len - prefixLen + (isUnc ? 1 : 0) + 1;
141
142                /* copy result without prefix into new buffer */
143                WCHAR *tmp = (WCHAR*)malloc(resultLen * sizeof(WCHAR));
144                if (tmp == NULL) {
145                    JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
146                    len = 0;
147                } else {
148                    WCHAR *p = result;
149                    p += prefixLen;
150                    if (isUnc) {
151                        WCHAR *p2 = tmp;
152                        p2[0] = L'\\';
153                        p2++;
154                        wcscpy(p2, p);
155                    } else {
156                        wcscpy(tmp, p);
157                    }
158                    free(result);
159                    result = tmp;
160                }
161            }
162        }
163
164        /* unable to get final path */
165        if (len == 0 && result != NULL) {
166            free(result);
167            result = NULL;
168        }
169    } else {
170        JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
171    }
172
173    error = GetLastError();
174    if (CloseHandle(h))
175        SetLastError(error);
176    return result;
177}
178
179/**
180 * Retrieves file information for the specified file. If the file is
181 * symbolic link then the information on fully resolved target is
182 * returned.
183 */
184static BOOL getFileInformation(const WCHAR *path,
185                               BY_HANDLE_FILE_INFORMATION *finfo)
186{
187    BOOL result;
188    DWORD error;
189    HANDLE h = CreateFileW(path,
190                           FILE_READ_ATTRIBUTES,
191                           FILE_SHARE_DELETE |
192                               FILE_SHARE_READ | FILE_SHARE_WRITE,
193                           NULL,
194                           OPEN_EXISTING,
195                           FILE_FLAG_BACKUP_SEMANTICS,
196                           NULL);
197    if (h == INVALID_HANDLE_VALUE)
198        return FALSE;
199    result = GetFileInformationByHandle(h, finfo);
200    error = GetLastError();
201    if (CloseHandle(h))
202        SetLastError(error);
203    return result;
204}
205
206/**
207 * If the given attributes are the attributes of a reparse point, then
208 * read and return the attributes of the special cases.
209 */
210DWORD getFinalAttributesIfReparsePoint(WCHAR *path, DWORD a)
211{
212    if ((a != INVALID_FILE_ATTRIBUTES) &&
213        ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
214    {
215        BY_HANDLE_FILE_INFORMATION finfo;
216        BOOL res = getFileInformation(path, &finfo);
217        a = (res) ? finfo.dwFileAttributes : INVALID_FILE_ATTRIBUTES;
218    }
219    return a;
220}
221
222/**
223 * Take special cases into account when retrieving the attributes
224 * of path
225 */
226DWORD getFinalAttributes(WCHAR *path)
227{
228    DWORD attr = INVALID_FILE_ATTRIBUTES;
229
230    WIN32_FILE_ATTRIBUTE_DATA wfad;
231    WIN32_FIND_DATAW wfd;
232    HANDLE h;
233
234    if (GetFileAttributesExW(path, GetFileExInfoStandard, &wfad)) {
235        attr = getFinalAttributesIfReparsePoint(path, wfad.dwFileAttributes);
236    } else {
237        DWORD lerr = GetLastError();
238        if ((lerr == ERROR_SHARING_VIOLATION || lerr == ERROR_ACCESS_DENIED) &&
239            (h = FindFirstFileW(path, &wfd)) != INVALID_HANDLE_VALUE) {
240            attr = getFinalAttributesIfReparsePoint(path, wfd.dwFileAttributes);
241            FindClose(h);
242        }
243    }
244    return attr;
245}
246
247JNIEXPORT jstring JNICALL
248Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this,
249                                           jstring pathname)
250{
251    jstring rv = NULL;
252    WCHAR canonicalPath[MAX_PATH_LENGTH];
253
254    WITH_UNICODE_STRING(env, pathname, path) {
255        /* we estimate the max length of memory needed as
256           "currentDir. length + pathname.length"
257         */
258        int len = (int)wcslen(path);
259        len += currentDirLength(path, len);
260        if (len  > MAX_PATH_LENGTH - 1) {
261            WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
262            if (cp != NULL) {
263                if (wcanonicalize(path, cp, len) >= 0) {
264                    rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
265                }
266                free(cp);
267            } else {
268                JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
269            }
270        } else if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) {
271            rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
272        }
273    } END_UNICODE_STRING(env, path);
274    if (rv == NULL && !(*env)->ExceptionCheck(env)) {
275        JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
276    }
277    return rv;
278}
279
280
281JNIEXPORT jstring JNICALL
282Java_java_io_WinNTFileSystem_canonicalizeWithPrefix0(JNIEnv *env, jobject this,
283                                                     jstring canonicalPrefixString,
284                                                     jstring pathWithCanonicalPrefixString)
285{
286    jstring rv = NULL;
287    WCHAR canonicalPath[MAX_PATH_LENGTH];
288    WITH_UNICODE_STRING(env, canonicalPrefixString, canonicalPrefix) {
289        WITH_UNICODE_STRING(env, pathWithCanonicalPrefixString, pathWithCanonicalPrefix) {
290            int len = (int)wcslen(canonicalPrefix) + MAX_PATH;
291            if (len > MAX_PATH_LENGTH) {
292                WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
293                if (cp != NULL) {
294                    if (wcanonicalizeWithPrefix(canonicalPrefix,
295                                                pathWithCanonicalPrefix,
296                                                cp, len) >= 0) {
297                      rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
298                    }
299                    free(cp);
300                } else {
301                    JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
302                }
303            } else if (wcanonicalizeWithPrefix(canonicalPrefix,
304                                               pathWithCanonicalPrefix,
305                                               canonicalPath, MAX_PATH_LENGTH) >= 0) {
306                rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
307            }
308        } END_UNICODE_STRING(env, pathWithCanonicalPrefix);
309    } END_UNICODE_STRING(env, canonicalPrefix);
310    if (rv == NULL && !(*env)->ExceptionCheck(env)) {
311        JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
312    }
313    return rv;
314}
315
316/* -- Attribute accessors -- */
317
318/* Check whether or not the file name in "path" is a Windows reserved
319   device name (CON, PRN, AUX, NUL, COM[1-9], LPT[1-9]) based on the
320   returned result from GetFullPathName, which should be in thr form of
321   "\\.\[ReservedDeviceName]" if the path represents a reserved device
322   name.
323   Note1: GetFullPathName doesn't think "CLOCK$" (which is no longer
324   important anyway) is a device name, so we don't check it here.
325   GetFileAttributesEx will catch it later by returning 0 on NT/XP/
326   200X.
327
328   Note2: Theoretically the implementation could just lookup the table
329   below linearly if the first 4 characters of the fullpath returned
330   from GetFullPathName are "\\.\". The current implementation should
331   achieve the same result. If Microsoft add more names into their
332   reserved device name repository in the future, which probably will
333   never happen, we will need to revisit the lookup implementation.
334
335static WCHAR* ReservedDEviceNames[] = {
336    L"CON", L"PRN", L"AUX", L"NUL",
337    L"COM1", L"COM2", L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", L"COM8", L"COM9",
338    L"LPT1", L"LPT2", L"LPT3", L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9",
339    L"CLOCK$"
340};
341 */
342
343static BOOL isReservedDeviceNameW(WCHAR* path) {
344#define BUFSIZE 9
345    WCHAR buf[BUFSIZE];
346    WCHAR *lpf = NULL;
347    DWORD retLen = GetFullPathNameW(path,
348                                   BUFSIZE,
349                                   buf,
350                                   &lpf);
351    if ((retLen == BUFSIZE - 1 || retLen == BUFSIZE - 2) &&
352        buf[0] == L'\\' && buf[1] == L'\\' &&
353        buf[2] == L'.' && buf[3] == L'\\') {
354        WCHAR* dname = _wcsupr(buf + 4);
355        if (wcscmp(dname, L"CON") == 0 ||
356            wcscmp(dname, L"PRN") == 0 ||
357            wcscmp(dname, L"AUX") == 0 ||
358            wcscmp(dname, L"NUL") == 0)
359            return TRUE;
360        if ((wcsncmp(dname, L"COM", 3) == 0 ||
361             wcsncmp(dname, L"LPT", 3) == 0) &&
362            dname[3] - L'0' > 0 &&
363            dname[3] - L'0' <= 9)
364            return TRUE;
365    }
366    return FALSE;
367}
368
369JNIEXPORT jint JNICALL
370Java_java_io_WinNTFileSystem_getBooleanAttributes(JNIEnv *env, jobject this,
371                                                  jobject file)
372{
373    jint rv = 0;
374
375    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
376    if (pathbuf == NULL)
377        return rv;
378    if (!isReservedDeviceNameW(pathbuf)) {
379        DWORD a = getFinalAttributes(pathbuf);
380        if (a != INVALID_FILE_ATTRIBUTES) {
381            rv = (java_io_FileSystem_BA_EXISTS
382                | ((a & FILE_ATTRIBUTE_DIRECTORY)
383                    ? java_io_FileSystem_BA_DIRECTORY
384                    : java_io_FileSystem_BA_REGULAR)
385                | ((a & FILE_ATTRIBUTE_HIDDEN)
386                    ? java_io_FileSystem_BA_HIDDEN : 0));
387        }
388    }
389    free(pathbuf);
390    return rv;
391}
392
393
394JNIEXPORT jboolean
395JNICALL Java_java_io_WinNTFileSystem_checkAccess(JNIEnv *env, jobject this,
396                                                 jobject file, jint access)
397{
398    DWORD attr;
399    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
400    if (pathbuf == NULL)
401        return JNI_FALSE;
402    attr = GetFileAttributesW(pathbuf);
403    attr = getFinalAttributesIfReparsePoint(pathbuf, attr);
404    free(pathbuf);
405    if (attr == INVALID_FILE_ATTRIBUTES)
406        return JNI_FALSE;
407    switch (access) {
408    case java_io_FileSystem_ACCESS_READ:
409    case java_io_FileSystem_ACCESS_EXECUTE:
410        return JNI_TRUE;
411    case java_io_FileSystem_ACCESS_WRITE:
412        /* Read-only attribute ignored on directories */
413        if ((attr & FILE_ATTRIBUTE_DIRECTORY) ||
414            (attr & FILE_ATTRIBUTE_READONLY) == 0)
415            return JNI_TRUE;
416        else
417            return JNI_FALSE;
418    default:
419        assert(0);
420        return JNI_FALSE;
421    }
422}
423
424JNIEXPORT jboolean JNICALL
425Java_java_io_WinNTFileSystem_setPermission(JNIEnv *env, jobject this,
426                                           jobject file,
427                                           jint access,
428                                           jboolean enable,
429                                           jboolean owneronly)
430{
431    jboolean rv = JNI_FALSE;
432    WCHAR *pathbuf;
433    DWORD a;
434    if (access == java_io_FileSystem_ACCESS_READ ||
435        access == java_io_FileSystem_ACCESS_EXECUTE) {
436        return enable;
437    }
438    pathbuf = fileToNTPath(env, file, ids.path);
439    if (pathbuf == NULL)
440        return JNI_FALSE;
441    a = GetFileAttributesW(pathbuf);
442
443    /* if reparse point, get final target */
444    if ((a != INVALID_FILE_ATTRIBUTES) &&
445        ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
446    {
447        WCHAR *fp = getFinalPath(env, pathbuf);
448        if (fp == NULL) {
449            a = INVALID_FILE_ATTRIBUTES;
450        } else {
451            free(pathbuf);
452            pathbuf = fp;
453            a = GetFileAttributesW(pathbuf);
454        }
455    }
456    if ((a != INVALID_FILE_ATTRIBUTES) &&
457        ((a & FILE_ATTRIBUTE_DIRECTORY) == 0))
458    {
459        if (enable)
460            a =  a & ~FILE_ATTRIBUTE_READONLY;
461        else
462            a =  a | FILE_ATTRIBUTE_READONLY;
463        if (SetFileAttributesW(pathbuf, a))
464            rv = JNI_TRUE;
465    }
466    free(pathbuf);
467    return rv;
468}
469
470JNIEXPORT jlong JNICALL
471Java_java_io_WinNTFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
472                                                 jobject file)
473{
474    jlong rv = 0;
475    ULARGE_INTEGER modTime;
476    FILETIME t;
477    HANDLE h;
478    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
479    if (pathbuf == NULL)
480        return rv;
481    h = CreateFileW(pathbuf,
482                    /* Device query access */
483                    0,
484                    /* Share it */
485                    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
486                    /* No security attributes */
487                    NULL,
488                    /* Open existing or fail */
489                    OPEN_EXISTING,
490                    /* Backup semantics for directories */
491                    FILE_FLAG_BACKUP_SEMANTICS,
492                    /* No template file */
493                    NULL);
494    if (h != INVALID_HANDLE_VALUE) {
495        if (GetFileTime(h, NULL, NULL, &t)) {
496            modTime.LowPart = (DWORD) t.dwLowDateTime;
497            modTime.HighPart = (LONG) t.dwHighDateTime;
498            rv = modTime.QuadPart / 10000;
499            rv -= 11644473600000;
500        }
501        CloseHandle(h);
502    }
503    free(pathbuf);
504    return rv;
505}
506
507JNIEXPORT jlong JNICALL
508Java_java_io_WinNTFileSystem_getLength(JNIEnv *env, jobject this, jobject file)
509{
510    jlong rv = 0;
511    WIN32_FILE_ATTRIBUTE_DATA wfad;
512    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
513    if (pathbuf == NULL)
514        return rv;
515    if (GetFileAttributesExW(pathbuf,
516                             GetFileExInfoStandard,
517                             &wfad)) {
518        if ((wfad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
519            rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow;
520        } else {
521            /* file is a reparse point so read attributes of final target */
522            BY_HANDLE_FILE_INFORMATION finfo;
523            if (getFileInformation(pathbuf, &finfo)) {
524                rv = finfo.nFileSizeHigh * ((jlong)MAXDWORD + 1) +
525                    finfo.nFileSizeLow;
526            }
527        }
528    } else {
529        if (GetLastError() == ERROR_SHARING_VIOLATION) {
530            /* The error is "share violation", which means the file/dir
531               must exists. Try _wstati64, we know this at least works
532               for pagefile.sys and hiberfil.sys.
533            */
534            struct _stati64 sb;
535            if (_wstati64(pathbuf, &sb) == 0) {
536                rv = sb.st_size;
537            }
538        }
539    }
540    free(pathbuf);
541    return rv;
542}
543
544/* -- File operations -- */
545
546JNIEXPORT jboolean JNICALL
547Java_java_io_WinNTFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
548                                                   jstring path)
549{
550    HANDLE h = NULL;
551    WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE);
552    if (pathbuf == NULL)
553        return JNI_FALSE;
554    if (isReservedDeviceNameW(pathbuf)) {
555        free(pathbuf);
556        return JNI_FALSE;
557    }
558    h = CreateFileW(
559        pathbuf,                              /* Wide char path name */
560        GENERIC_READ | GENERIC_WRITE,         /* Read and write permission */
561        FILE_SHARE_READ | FILE_SHARE_WRITE,   /* File sharing flags */
562        NULL,                                 /* Security attributes */
563        CREATE_NEW,                           /* creation disposition */
564        FILE_ATTRIBUTE_NORMAL |
565            FILE_FLAG_OPEN_REPARSE_POINT,     /* flags and attributes */
566        NULL);
567
568    if (h == INVALID_HANDLE_VALUE) {
569        DWORD error = GetLastError();
570        if ((error != ERROR_FILE_EXISTS) && (error != ERROR_ALREADY_EXISTS)) {
571            // return false rather than throwing an exception when there is
572            // an existing file.
573            DWORD a = GetFileAttributesW(pathbuf);
574            if (a == INVALID_FILE_ATTRIBUTES) {
575                SetLastError(error);
576                JNU_ThrowIOExceptionWithLastError(env, "Could not open file");
577            }
578        }
579        free(pathbuf);
580        return JNI_FALSE;
581    }
582    free(pathbuf);
583    CloseHandle(h);
584    return JNI_TRUE;
585}
586
587static int
588removeFileOrDirectory(const jchar *path)
589{
590    /* Returns 0 on success */
591    DWORD a;
592
593    SetFileAttributesW(path, FILE_ATTRIBUTE_NORMAL);
594    a = GetFileAttributesW(path);
595    if (a == INVALID_FILE_ATTRIBUTES) {
596        return 1;
597    } else if (a & FILE_ATTRIBUTE_DIRECTORY) {
598        return !RemoveDirectoryW(path);
599    } else {
600        return !DeleteFileW(path);
601    }
602}
603
604JNIEXPORT jboolean JNICALL
605Java_java_io_WinNTFileSystem_delete0(JNIEnv *env, jobject this, jobject file)
606{
607    jboolean rv = JNI_FALSE;
608    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
609    if (pathbuf == NULL) {
610        return JNI_FALSE;
611    }
612    if (removeFileOrDirectory(pathbuf) == 0) {
613        rv = JNI_TRUE;
614    }
615    free(pathbuf);
616    return rv;
617}
618
619JNIEXPORT jobjectArray JNICALL
620Java_java_io_WinNTFileSystem_list(JNIEnv *env, jobject this, jobject file)
621{
622    WCHAR *search_path;
623    HANDLE handle;
624    WIN32_FIND_DATAW find_data;
625    int len, maxlen;
626    jobjectArray rv, old;
627    DWORD fattr;
628    jstring name;
629    jclass str_class;
630    WCHAR *pathbuf;
631
632    str_class = JNU_ClassString(env);
633    CHECK_NULL_RETURN(str_class, NULL);
634
635    pathbuf = fileToNTPath(env, file, ids.path);
636    if (pathbuf == NULL)
637        return NULL;
638    search_path = (WCHAR*)malloc(2*wcslen(pathbuf) + 6);
639    if (search_path == 0) {
640        free (pathbuf);
641        errno = ENOMEM;
642        JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
643        return NULL;
644    }
645    wcscpy(search_path, pathbuf);
646    free(pathbuf);
647    fattr = GetFileAttributesW(search_path);
648    if (fattr == INVALID_FILE_ATTRIBUTES) {
649        free(search_path);
650        return NULL;
651    } else if ((fattr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
652        free(search_path);
653        return NULL;
654    }
655
656    /* Remove trailing space chars from directory name */
657    len = (int)wcslen(search_path);
658    while (search_path[len-1] == L' ') {
659        len--;
660    }
661    search_path[len] = 0;
662
663    /* Append "*", or possibly "\\*", to path */
664    if ((search_path[0] == L'\\' && search_path[1] == L'\0') ||
665        (search_path[1] == L':'
666        && (search_path[2] == L'\0'
667        || (search_path[2] == L'\\' && search_path[3] == L'\0')))) {
668        /* No '\\' needed for cases like "\" or "Z:" or "Z:\" */
669        wcscat(search_path, L"*");
670    } else {
671        wcscat(search_path, L"\\*");
672    }
673
674    /* Open handle to the first file */
675    handle = FindFirstFileW(search_path, &find_data);
676    free(search_path);
677    if (handle == INVALID_HANDLE_VALUE) {
678        if (GetLastError() != ERROR_FILE_NOT_FOUND) {
679            // error
680            return NULL;
681        } else {
682            // No files found - return an empty array
683            rv = (*env)->NewObjectArray(env, 0, str_class, NULL);
684            return rv;
685        }
686    }
687
688    /* Allocate an initial String array */
689    len = 0;
690    maxlen = 16;
691    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
692    if (rv == NULL) // Couldn't allocate an array
693        return NULL;
694    /* Scan the directory */
695    do {
696        if (!wcscmp(find_data.cFileName, L".")
697                                || !wcscmp(find_data.cFileName, L".."))
698           continue;
699        name = (*env)->NewString(env, find_data.cFileName,
700                                 (jsize)wcslen(find_data.cFileName));
701        if (name == NULL)
702            return NULL; // error;
703        if (len == maxlen) {
704            old = rv;
705            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
706            if (rv == NULL || JNU_CopyObjectArray(env, rv, old, len) < 0)
707                return NULL; // error
708            (*env)->DeleteLocalRef(env, old);
709        }
710        (*env)->SetObjectArrayElement(env, rv, len++, name);
711        (*env)->DeleteLocalRef(env, name);
712
713    } while (FindNextFileW(handle, &find_data));
714
715    if (GetLastError() != ERROR_NO_MORE_FILES)
716        return NULL; // error
717    FindClose(handle);
718
719    if (len < maxlen) {
720        /* Copy the final results into an appropriately-sized array */
721        old = rv;
722        rv = (*env)->NewObjectArray(env, len, str_class, NULL);
723        if (rv == NULL)
724            return NULL; /* error */
725        if (JNU_CopyObjectArray(env, rv, old, len) < 0)
726            return NULL; /* error */
727    }
728    return rv;
729}
730
731
732JNIEXPORT jboolean JNICALL
733Java_java_io_WinNTFileSystem_createDirectory(JNIEnv *env, jobject this,
734                                             jobject file)
735{
736    BOOL h = FALSE;
737    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
738    if (pathbuf == NULL) {
739        /* Exception is pending */
740        return JNI_FALSE;
741    }
742    h = CreateDirectoryW(pathbuf, NULL);
743    free(pathbuf);
744
745    if (h == 0) {
746        return JNI_FALSE;
747    }
748
749    return JNI_TRUE;
750}
751
752
753JNIEXPORT jboolean JNICALL
754Java_java_io_WinNTFileSystem_rename0(JNIEnv *env, jobject this, jobject from,
755                                     jobject to)
756{
757
758    jboolean rv = JNI_FALSE;
759    WCHAR *frompath = fileToNTPath(env, from, ids.path);
760    WCHAR *topath = fileToNTPath(env, to, ids.path);
761    if (frompath != NULL && topath != NULL && _wrename(frompath, topath) == 0) {
762        rv = JNI_TRUE;
763    }
764    free(frompath);
765    free(topath);
766    return rv;
767}
768
769
770JNIEXPORT jboolean JNICALL
771Java_java_io_WinNTFileSystem_setLastModifiedTime(JNIEnv *env, jobject this,
772                                                 jobject file, jlong time)
773{
774    jboolean rv = JNI_FALSE;
775    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
776    HANDLE h;
777    if (pathbuf == NULL)
778        return JNI_FALSE;
779    h = CreateFileW(pathbuf,
780                    FILE_WRITE_ATTRIBUTES,
781                    FILE_SHARE_READ | FILE_SHARE_WRITE,
782                    NULL,
783                    OPEN_EXISTING,
784                    FILE_FLAG_BACKUP_SEMANTICS,
785                    0);
786    if (h != INVALID_HANDLE_VALUE) {
787        ULARGE_INTEGER modTime;
788        FILETIME t;
789        modTime.QuadPart = (time + 11644473600000L) * 10000L;
790        t.dwLowDateTime = (DWORD)modTime.LowPart;
791        t.dwHighDateTime = (DWORD)modTime.HighPart;
792        if (SetFileTime(h, NULL, NULL, &t)) {
793            rv = JNI_TRUE;
794        }
795        CloseHandle(h);
796    }
797    free(pathbuf);
798
799    return rv;
800}
801
802
803JNIEXPORT jboolean JNICALL
804Java_java_io_WinNTFileSystem_setReadOnly(JNIEnv *env, jobject this,
805                                         jobject file)
806{
807    jboolean rv = JNI_FALSE;
808    DWORD a;
809    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
810    if (pathbuf == NULL)
811        return JNI_FALSE;
812    a = GetFileAttributesW(pathbuf);
813
814    /* if reparse point, get final target */
815    if ((a != INVALID_FILE_ATTRIBUTES) &&
816        ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
817    {
818        WCHAR *fp = getFinalPath(env, pathbuf);
819        if (fp == NULL) {
820            a = INVALID_FILE_ATTRIBUTES;
821        } else {
822            free(pathbuf);
823            pathbuf = fp;
824            a = GetFileAttributesW(pathbuf);
825        }
826    }
827
828    if ((a != INVALID_FILE_ATTRIBUTES) &&
829        ((a & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
830        if (SetFileAttributesW(pathbuf, a | FILE_ATTRIBUTE_READONLY))
831            rv = JNI_TRUE;
832    }
833    free(pathbuf);
834    return rv;
835}
836
837/* -- Filesystem interface -- */
838
839
840JNIEXPORT jobject JNICALL
841Java_java_io_WinNTFileSystem_getDriveDirectory(JNIEnv *env, jobject this,
842                                               jint drive)
843{
844    jstring ret = NULL;
845    jchar *p = currentDir(drive);
846    jchar *pf = p;
847    if (p == NULL) return NULL;
848    if (iswalpha(*p) && (p[1] == L':')) p += 2;
849    ret = (*env)->NewString(env, p, (jsize)wcslen(p));
850    free (pf);
851    return ret;
852}
853
854JNIEXPORT jint JNICALL
855Java_java_io_WinNTFileSystem_listRoots0(JNIEnv *env, jclass ignored)
856{
857    return GetLogicalDrives();
858}
859
860JNIEXPORT jlong JNICALL
861Java_java_io_WinNTFileSystem_getSpace0(JNIEnv *env, jobject this,
862                                       jobject file, jint t)
863{
864    WCHAR volname[MAX_PATH_LENGTH + 1];
865    jlong rv = 0L;
866    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
867
868    if (GetVolumePathNameW(pathbuf, volname, MAX_PATH_LENGTH)) {
869        ULARGE_INTEGER totalSpace, freeSpace, usableSpace;
870        if (GetDiskFreeSpaceExW(volname, &usableSpace, &totalSpace, &freeSpace)) {
871            switch(t) {
872            case java_io_FileSystem_SPACE_TOTAL:
873                rv = long_to_jlong(totalSpace.QuadPart);
874                break;
875            case java_io_FileSystem_SPACE_FREE:
876                rv = long_to_jlong(freeSpace.QuadPart);
877                break;
878            case java_io_FileSystem_SPACE_USABLE:
879                rv = long_to_jlong(usableSpace.QuadPart);
880                break;
881            default:
882                assert(0);
883            }
884        }
885    }
886
887    free(pathbuf);
888    return rv;
889}
890
891// pathname is expected to be either null or to contain the root
892// of the path terminated by a backslash
893JNIEXPORT jint JNICALL
894Java_java_io_WinNTFileSystem_getNameMax0(JNIEnv *env, jobject this,
895                                         jstring pathname)
896{
897    BOOL res = 0;
898    DWORD maxComponentLength;
899
900    if (pathname == NULL) {
901            res = GetVolumeInformationW(NULL,
902                                        NULL,
903                                        0,
904                                        NULL,
905                                        &maxComponentLength,
906                                        NULL,
907                                        NULL,
908                                        0);
909    } else {
910        WITH_UNICODE_STRING(env, pathname, path) {
911            res = GetVolumeInformationW(path,
912                                        NULL,
913                                        0,
914                                        NULL,
915                                        &maxComponentLength,
916                                        NULL,
917                                        NULL,
918                                        0);
919        } END_UNICODE_STRING(env, path);
920    }
921
922    if (res == 0) {
923        JNU_ThrowIOExceptionWithLastError(env,
924            "Could not get maximum component length");
925    }
926
927    return (jint)maxComponentLength;
928}
929