Skip to content

Win long path #236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkgs/io_file/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@
"<fcntl.h>": [
"AT_FDCWD",
"AT_REMOVEDIR"
],
"<limits.h>": [
"PATH_MAX"
]
}
3 changes: 3 additions & 0 deletions pkgs/io_file/dart_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
concurrency: 1
reporter: compact
test-randomize-ordering-seed: random
3 changes: 3 additions & 0 deletions pkgs/io_file/lib/src/constant_bindings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ external int get_AT_FDCWD();
@ffi.Native<ffi.Int64 Function()>(symbol: 'libc_shim_get_AT_REMOVEDIR')
external int get_AT_REMOVEDIR();

@ffi.Native<ffi.Int64 Function()>(symbol: 'libc_shim_get_PATH_MAX')
external int get_PATH_MAX();

@ffi.Native<ffi.Int64 Function()>(symbol: 'libc_shim_get_S_IEXEC')
external int get_S_IEXEC();

Expand Down
9 changes: 9 additions & 0 deletions pkgs/io_file/lib/src/constants.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ int get AT_REMOVEDIR {
}
}

int get PATH_MAX {
final v = get_PATH_MAX();
if (v == libc_shim_UNDEFINED) {
throw UnsupportedError('PATH_MAX');
} else {
return v;
}
}

int get S_IEXEC {
final v = get_S_IEXEC();
if (v == libc_shim_UNDEFINED) {
Expand Down
18 changes: 18 additions & 0 deletions pkgs/io_file/lib/src/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ base class FileSystem {
throw UnsupportedError('createTemporaryDirectory');
}

/// The current
/// [working directory](https://en.wikipedia.org/wiki/Working_directory) of
/// the Dart process.
///
/// Setting the value of this field will change the working directory for
/// *all* isolates.
///
/// On Windows, unless
/// [long paths are enabled](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation),
/// the maximum length of the [currentDirectory] path is 260 characters.
String get currentDirectory {
throw UnsupportedError('currentDirectory');
}

set currentDirectory(String path) {
throw UnsupportedError('currentDirectory');
}

/// Deletes the directory at the given path.
///
/// If `path` is a directory but the directory is not empty, then
Expand Down
8 changes: 8 additions & 0 deletions pkgs/io_file/lib/src/libc_bindings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,17 @@ external ffi.Pointer<ffi.Char> mkdtemp(ffi.Pointer<ffi.Char> template);
external ffi.Pointer<ffi.Char> strerror(int errnum);

/// <unistd.h>
@ffi.Native<ffi.Int Function(ffi.Pointer<ffi.Char>)>(symbol: 'libc_shim_chdir')
external int chdir(ffi.Pointer<ffi.Char> path);

@ffi.Native<ffi.Int Function(ffi.Int)>(symbol: 'libc_shim_close')
external int close(int fd);

@ffi.Native<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>, ffi.Int64)>(
symbol: 'libc_shim_getcwd',
)
external ffi.Pointer<ffi.Char> getcwd(ffi.Pointer<ffi.Char> buf, int size);

@ffi.Native<ffi.Int Function(ffi.Int, ffi.Pointer<ffi.Char>, ffi.Int)>(
symbol: 'libc_shim_unlinkat',
)
Expand Down
36 changes: 29 additions & 7 deletions pkgs/io_file/lib/src/vm_posix_file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const _defaultMode = 438; // => 0666 => rw-rw-rw-
/// The default `mode` to use when creating a directory.
const _defaultDirectoryMode = 511; // => 0777 => rwxrwxrwx

Exception _getError(int err, String message, String path) {
Exception _getError(int err, String message, [String? path]) {
//TODO(brianquinlan): In the long-term, do we need to avoid exceptions that
// are part of `dart:io`? Can we move those exceptions into a different
// namespace?
Expand All @@ -30,12 +30,16 @@ Exception _getError(int err, String message, String path) {
err,
);

if (err == libc.EPERM || err == libc.EACCES) {
return io.PathAccessException(path, osError, message);
} else if (err == libc.EEXIST) {
return io.PathExistsException(path, osError, message);
} else if (err == libc.ENOENT) {
return io.PathNotFoundException(path, osError, message);
if (path != null) {
if (err == libc.EPERM || err == libc.EACCES) {
return io.PathAccessException(path, osError, message);
} else if (err == libc.EEXIST) {
return io.PathExistsException(path, osError, message);
} else if (err == libc.ENOENT) {
return io.PathNotFoundException(path, osError, message);
} else {
return io.FileSystemException(message, path, osError);
}
} else {
return io.FileSystemException(message, path, osError);
}
Expand Down Expand Up @@ -95,6 +99,24 @@ base class PosixFileSystem extends FileSystem {
}
});

@override
set currentDirectory(String path) => ffi.using((arena) {
if (libc.chdir(path.toNativeUtf8(allocator: arena).cast()) == -1) {
final errno = libc.errno;
throw _getError(errno, 'chdir failed', path);
}
});

@override
String get currentDirectory => ffi.using((arena) {
final buffer = arena<Char>(libc.PATH_MAX);
if (libc.getcwd(buffer, libc.PATH_MAX) == nullptr) {
final errno = libc.errno;
throw _getError(errno, 'getcwd failed', null);
}
return buffer.cast<ffi.Utf8>().toDartString();
});

@override
String createTemporaryDirectory({String? parent, String? prefix}) =>
ffi.using((arena) {
Expand Down
72 changes: 68 additions & 4 deletions pkgs/io_file/lib/src/vm_windows_file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,33 @@ void _primeGetLastError() {
win32.GetLastError();
}

Pointer<Utf16> _apiPath(String path, Allocator a) =>
(r'\\?\' + p.canonicalize(path)).toNativeUtf16(allocator: a);

/*
Pointer<Utf16> _apiPath(String path, Allocator a) {
var length = 256;
var utf16Path = path.toNativeUtf16(allocator: a);
do {
final buffer = win32.wsalloc(length);
try {
final result = win32.GetFullPathName(utf16Path, length, buffer, nullptr);
if (result == 0) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'GetFullPathName failed', path);
}
if (result < length) {
win32.PathAllocCanonicalize()
} else {
length = result;
}
} finally {
win32.free(buffer);
}
} while (true);
}
*/

String _formatMessage(int errorCode) {
final buffer = win32.wsalloc(1024);
try {
Expand Down Expand Up @@ -130,8 +157,7 @@ base class WindowsFileSystem extends FileSystem {
void createDirectory(String path) => using((arena) {
_primeGetLastError();

if (win32.CreateDirectory(path.toNativeUtf16(allocator: arena), nullptr) ==
win32.FALSE) {
if (win32.CreateDirectory(_apiPath(path, arena), nullptr) == win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'create directory failed', path);
}
Expand All @@ -148,6 +174,44 @@ base class WindowsFileSystem extends FileSystem {
return path;
}

@override
set currentDirectory(String path) => using((arena) {
// XXX
// SetCurrentDirectory does not actually support paths larger than MAX_PATH,
// this limitation is due to the size of the internal buffer used for
// storing
// current directory. In Windows 10, version 1607, changes have been made
// to the OS to lift MAX_PATH limitations from file and directory management
// APIs, but both application and OS need to opt-in into new behavior.
// See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later

_primeGetLastError();
if (win32.SetCurrentDirectory(path.toNativeUtf16()) == win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'SetCurrentDirectory failed', path);
}
});

@override
String get currentDirectory => using((arena) {
_primeGetLastError();

var length = 256;
do {
final buffer = win32.wsalloc(length);
try {
final result = win32.GetCurrentDirectory(length, buffer);
if (result < length) {
return buffer.toDartString();
} else {
length = result;
}
} finally {
win32.free(buffer);
}
} while (true);
});

@override
void removeDirectory(String path) => using((arena) {
_primeGetLastError();
Expand Down Expand Up @@ -247,7 +311,7 @@ base class WindowsFileSystem extends FileSystem {
_primeGetLastError();

final f = win32.CreateFile(
path.toNativeUtf16(),
_apiPath(path, arena),
win32.GENERIC_READ | win32.FILE_SHARE_READ,
win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE,
nullptr,
Expand Down Expand Up @@ -383,7 +447,7 @@ base class WindowsFileSystem extends FileSystem {
};

final f = win32.CreateFile(
path.toNativeUtf16(allocator: arena),
_apiPath(path, arena),
mode == WriteMode.appendExisting
? win32.FILE_APPEND_DATA
: win32.FILE_GENERIC_WRITE,
Expand Down
2 changes: 1 addition & 1 deletion pkgs/io_file/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies:
logging: ^1.3.0
native_toolchain_c: ^0.16.0
path: ^1.9.1
win32: ^5.11.0
win32: ^5.13.0

dev_dependencies:
args: ^2.7.0
Expand Down
8 changes: 8 additions & 0 deletions pkgs/io_file/src/constants.g.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
int64_t libc_shim_get_EACCES(void) {
#ifdef EACCES
Expand Down Expand Up @@ -106,6 +107,13 @@ int64_t libc_shim_get_AT_REMOVEDIR(void) {
#endif
return libc_shim_UNDEFINED;
}
int64_t libc_shim_get_PATH_MAX(void) {
#ifdef PATH_MAX
assert(PATH_MAX != libc_shim_UNDEFINED);
return PATH_MAX;
#endif
return libc_shim_UNDEFINED;
}
int64_t libc_shim_get_S_IEXEC(void) {
#ifdef S_IEXEC
assert(S_IEXEC != libc_shim_UNDEFINED);
Expand Down
2 changes: 2 additions & 0 deletions pkgs/io_file/src/constants.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ int64_t libc_shim_get_AT_FDCWD(void);
__attribute__((visibility("default"))) __attribute__((used))
int64_t libc_shim_get_AT_REMOVEDIR(void);
__attribute__((visibility("default"))) __attribute__((used))
int64_t libc_shim_get_PATH_MAX(void);
__attribute__((visibility("default"))) __attribute__((used))
int64_t libc_shim_get_S_IEXEC(void);
__attribute__((visibility("default"))) __attribute__((used))
int64_t libc_shim_get_S_IFBLK(void);
Expand Down
4 changes: 4 additions & 0 deletions pkgs/io_file/src/libc_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,12 @@ char *libc_shim_strerror(int errnum) { return strerror(errnum); }

// <unistd.h>

int libc_shim_chdir(const char *path) { return chdir(path); }

int libc_shim_close(int fd) { return close(fd); }

char *libc_shim_getcwd(char *buf, int64_t size) { return getcwd(buf, size); }

int libc_shim_unlinkat(int dirfd, const char *pathname, int flags) {
return unlinkat(dirfd, pathname, flags);
}
2 changes: 2 additions & 0 deletions pkgs/io_file/src/libc_shim.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ LIBC_SHIM_EXPORT char *libc_shim_strerror(int errnum);

// <unistd.h>

LIBC_SHIM_EXPORT int libc_shim_chdir(const char *path);
LIBC_SHIM_EXPORT int libc_shim_close(int fd);
LIBC_SHIM_EXPORT char *libc_shim_getcwd(char *buf, int64_t size);
LIBC_SHIM_EXPORT int libc_shim_unlinkat(int dirfd, const char *pathname,
int flags);
Loading
Loading