@@ -87,7 +87,19 @@ wchar_t* add_prefix(wchar_t* path, int path_len, wchar_t* prefix) {
8787 int str_len = path_len + prefix_len;
8888 wchar_t * str = (wchar_t *)malloc (sizeof (wchar_t ) * (str_len + 1 ));
8989 wcscpy_s (str, str_len + 1 , prefix);
90- wcscat_s (str, str_len + 1 , path);
90+ wcsncat_s (str, str_len + 1 , path, path_len);
91+ return str;
92+ }
93+
94+ //
95+ // Returns a UTF-16 string that is the concatenation of |path| and |suffix|.
96+ //
97+ wchar_t * add_suffix (wchar_t * path, int path_len, wchar_t * suffix) {
98+ int suffix_len = wcslen (suffix);
99+ int str_len = path_len + suffix_len;
100+ wchar_t * str = (wchar_t *)malloc (sizeof (wchar_t ) * (str_len + 1 ));
101+ wcsncpy_s (str, str_len + 1 , path, path_len);
102+ wcscat_s (str, str_len + 1 , suffix);
91103 return str;
92104}
93105
@@ -127,6 +139,125 @@ wchar_t* java_to_wchar_path(JNIEnv *env, jstring string, jobject result) {
127139 }
128140}
129141
142+ //
143+ // Returns 'true' if a file, given its attributes, is a Windows Symbolic Link.
144+ //
145+ bool is_file_symlink (DWORD dwFileAttributes, DWORD reparseTagData) {
146+ //
147+ // See https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags
148+ // IO_REPARSE_TAG_SYMLINK (0xA000000C)
149+ //
150+ return
151+ ((dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT) &&
152+ (reparseTagData == IO_REPARSE_TAG_SYMLINK);
153+ }
154+
155+ jlong lastModifiedNanos (FILETIME* time) {
156+ return ((jlong)time->dwHighDateTime << 32 ) | time->dwLowDateTime ;
157+ }
158+
159+ jlong lastModifiedNanos (LARGE_INTEGER* time) {
160+ return ((jlong)time->HighPart << 32 ) | time->LowPart ;
161+ }
162+
163+ typedef struct file_stat {
164+ int fileType;
165+ LONG64 lastModified;
166+ LONG64 size;
167+ } file_stat_t ;
168+
169+ //
170+ // Retrieves the file attributes for the file specified by |pathStr|.
171+ // If |followLink| is true, symbolic link targets are resolved.
172+ //
173+ // * Returns ERROR_SUCCESS if the file exists and file attributes can be retrieved,
174+ // * Returns ERROR_SUCCESS with a FILE_TYPE_MISSING if the file does not exist,
175+ // * Returns a Win32 error code in all other cases.
176+ //
177+ DWORD get_file_stat (wchar_t * pathStr, jboolean followLink, file_stat_t * pFileStat) {
178+ #ifdef WINDOWS_MIN
179+ WIN32_FILE_ATTRIBUTE_DATA attr;
180+ BOOL ok = GetFileAttributesExW (pathStr, GetFileExInfoStandard, &attr);
181+ if (!ok) {
182+ DWORD error = GetLastError ();
183+ if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
184+ // Treat device with no media as missing
185+ pFileStat->lastModified = 0 ;
186+ pFileStat->size = 0 ;
187+ pFileStat->fileType = FILE_TYPE_MISSING;
188+ return ERROR_SUCCESS;
189+ }
190+ return error;
191+ }
192+ pFileStat->lastModified = lastModifiedNanos (&attr.ftLastWriteTime );
193+ if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
194+ pFileStat->size = 0 ;
195+ pFileStat->fileType = FILE_TYPE_DIRECTORY;
196+ } else {
197+ pFileStat->size = ((LONG64)attr.nFileSizeHigh << 32 ) | attr.nFileSizeLow ;
198+ pFileStat->fileType = FILE_TYPE_FILE;
199+ }
200+ return ERROR_SUCCESS;
201+ #else // WINDOWS_MIN: Windows Vista+ support for symlinks
202+ DWORD dwFlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS;
203+ if (!followLink) {
204+ dwFlagsAndAttributes |= FILE_FLAG_OPEN_REPARSE_POINT;
205+ }
206+ HANDLE fileHandle = CreateFileW (
207+ pathStr, // lpFileName
208+ GENERIC_READ, // dwDesiredAccess
209+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode
210+ NULL , // lpSecurityAttributes
211+ OPEN_EXISTING, // dwCreationDisposition
212+ dwFlagsAndAttributes, // dwFlagsAndAttributes
213+ NULL // hTemplateFile
214+ );
215+ if (fileHandle == INVALID_HANDLE_VALUE) {
216+ DWORD error = GetLastError ();
217+ if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
218+ // Treat device with no media as missing
219+ pFileStat->lastModified = 0 ;
220+ pFileStat->size = 0 ;
221+ pFileStat->fileType = FILE_TYPE_MISSING;
222+ return ERROR_SUCCESS;
223+ }
224+ return error;
225+ }
226+
227+ // This call allows retrieving almost everything except for the reparseTag
228+ BY_HANDLE_FILE_INFORMATION fileInfo;
229+ BOOL ok = GetFileInformationByHandle (fileHandle, &fileInfo);
230+ if (!ok) {
231+ DWORD error = GetLastError ();
232+ CloseHandle (fileHandle);
233+ return error;
234+ }
235+
236+ // This call allows retrieving the reparse tag
237+ FILE_ATTRIBUTE_TAG_INFO fileTagInfo;
238+ ok = GetFileInformationByHandleEx (fileHandle, FileAttributeTagInfo, &fileTagInfo, sizeof (fileTagInfo));
239+ if (!ok) {
240+ DWORD error = GetLastError ();
241+ CloseHandle (fileHandle);
242+ return error;
243+ }
244+
245+ CloseHandle (fileHandle);
246+
247+ pFileStat->lastModified = lastModifiedNanos (&fileInfo.ftLastWriteTime );
248+ pFileStat->size = 0 ;
249+ if (is_file_symlink (fileTagInfo.FileAttributes , fileTagInfo.ReparseTag )) {
250+ pFileStat->fileType = FILE_TYPE_SYMLINK;
251+ } else if (fileTagInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
252+ pFileStat->fileType = FILE_TYPE_DIRECTORY;
253+ } else {
254+ pFileStat->size = ((LONG64)fileInfo.nFileSizeHigh << 32 ) | fileInfo.nFileSizeLow ;
255+ pFileStat->fileType = FILE_TYPE_FILE;
256+ }
257+ return ERROR_SUCCESS;
258+ #endif
259+ }
260+
130261JNIEXPORT void JNICALL
131262Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo (JNIEnv *env, jclass target, jobject info, jobject result) {
132263 jclass infoClass = env->GetObjectClass (info);
@@ -387,44 +518,28 @@ Java_net_rubygrapefruit_platform_internal_jni_FileEventFunctions_closeWatch(JNIE
387518 free (details);
388519}
389520
390- jlong lastModifiedNanos (FILETIME* time) {
391- return ((jlong)time->dwHighDateTime << 32 ) | time->dwLowDateTime ;
392- }
393-
394521JNIEXPORT void JNICALL
395- Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat (JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) {
522+ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat (JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject dest, jobject result) {
396523 jclass destClass = env->GetObjectClass (dest);
397524 jmethodID mid = env->GetMethodID (destClass, " details" , " (IJJ)V" );
398525 if (mid == NULL ) {
399526 mark_failed_with_message (env, " could not find method" , result);
400527 return ;
401528 }
402529
403- WIN32_FILE_ATTRIBUTE_DATA attr;
404530 wchar_t * pathStr = java_to_wchar_path (env, path, result);
405- BOOL ok = GetFileAttributesExW (pathStr, GetFileExInfoStandard, &attr);
531+ file_stat_t fileStat;
532+ DWORD errorCode = get_file_stat (pathStr, followLink, &fileStat);
406533 free (pathStr);
407- if (!ok) {
408- DWORD error = GetLastError ();
409- if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND || error == ERROR_NOT_READY) {
410- // Treat device with no media as missing
411- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_MISSING, (jlong)0 , (jlong)0 );
412- return ;
413- }
414- mark_failed_with_errno (env, " could not file attributes" , result);
534+ if (errorCode != ERROR_SUCCESS) {
535+ mark_failed_with_code (env, " could not file attributes" , errorCode, NULL , result);
415536 return ;
416537 }
417- jlong lastModified = lastModifiedNanos (&attr.ftLastWriteTime );
418- if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
419- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_DIRECTORY, (jlong)0 , lastModified);
420- } else {
421- jlong size = ((jlong)attr.nFileSizeHigh << 32 ) | attr.nFileSizeLow ;
422- env->CallVoidMethod (dest, mid, (jint)FILE_TYPE_FILE, size, lastModified);
423- }
538+ env->CallVoidMethod (dest, mid, fileStat.fileType , fileStat.size , fileStat.lastModified );
424539}
425540
426541JNIEXPORT void JNICALL
427- Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir (JNIEnv *env, jclass target, jstring path, jobject contents, jobject result) {
542+ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir (JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject contents, jobject result) {
428543 jclass contentsClass = env->GetObjectClass (contents);
429544 jmethodID mid = env->GetMethodID (contentsClass, " addFile" , " (Ljava/lang/String;IJJ)V" );
430545 if (mid == NULL ) {
@@ -434,29 +549,55 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn
434549
435550 WIN32_FIND_DATAW entry;
436551 wchar_t * pathStr = java_to_wchar_path (env, path, result);
437- HANDLE dirHandle = FindFirstFileW (pathStr, &entry );
552+ wchar_t * patternStr = add_suffix (pathStr, wcslen (pathStr), L" \\ * " );
438553 free (pathStr);
554+ HANDLE dirHandle = FindFirstFileW (patternStr, &entry);
439555 if (dirHandle == INVALID_HANDLE_VALUE) {
440556 mark_failed_with_errno (env, " could not open directory" , result);
557+ free (patternStr);
441558 return ;
442559 }
443560
444561 do {
445562 if (wcscmp (L" ." , entry.cFileName ) == 0 || wcscmp (L" .." , entry.cFileName ) == 0 ) {
446563 continue ;
447564 }
565+
566+ // If entry is a symbolic link, we may have to get the attributes of the link target
567+ bool isSymLink = is_file_symlink (entry.dwFileAttributes , entry.dwReserved0 );
568+ file_stat_t fileInfo;
569+ if (isSymLink && followLink) {
570+ // We use patternStr minus the last character ("*") to create the absolute path of the child entry
571+ wchar_t * childPathStr = add_suffix (patternStr, wcslen (patternStr) - 1 , entry.cFileName );
572+ DWORD errorCode = get_file_stat (childPathStr, true , &fileInfo);
573+ free (childPathStr);
574+ if (errorCode != ERROR_SUCCESS) {
575+ // If we can't dereference the symbolic link, create a "missing file" entry
576+ fileInfo.fileType = FILE_TYPE_MISSING;
577+ fileInfo.size = 0 ;
578+ fileInfo.lastModified = 0 ;
579+ }
580+ } else {
581+ fileInfo.fileType = isSymLink ?
582+ FILE_TYPE_SYMLINK :
583+ (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
584+ FILE_TYPE_DIRECTORY :
585+ FILE_TYPE_FILE;
586+ fileInfo.lastModified = lastModifiedNanos (&entry.ftLastWriteTime );
587+ fileInfo.size = ((jlong)entry.nFileSizeHigh << 32 ) | entry.nFileSizeLow ;
588+ }
589+
590+ // Add entry
448591 jstring childName = wchar_to_java (env, entry.cFileName , wcslen (entry.cFileName ), result);
449- jint type = (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_TYPE_DIRECTORY : FILE_TYPE_FILE;
450- jlong lastModified = lastModifiedNanos (&entry.ftLastWriteTime );
451- jlong size = ((jlong)entry.nFileSizeHigh << 32 ) | entry.nFileSizeLow ;
452- env->CallVoidMethod (contents, mid, childName, type, size, lastModified);
592+ env->CallVoidMethod (contents, mid, childName, fileInfo.fileType , fileInfo.size , fileInfo.lastModified );
453593 } while (FindNextFileW (dirHandle, &entry) != 0 );
454594
455595 DWORD error = GetLastError ();
456596 if (error != ERROR_NO_MORE_FILES ) {
457597 mark_failed_with_errno (env, " could not read next directory entry" , result);
458598 }
459599
600+ free (patternStr);
460601 FindClose (dirHandle);
461602}
462603
0 commit comments