Skip to content

Commit 704a5c3

Browse files
authored
Adds support for fetching metadata on POSIX (#204)
1 parent 55f5a5a commit 704a5c3

File tree

7 files changed

+509
-35
lines changed

7 files changed

+509
-35
lines changed

pkgs/io_file/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ See
2222
| delete tree | | | | || | |
2323
| enum dir contents | | | | | | | |
2424
| exists | | | | | | |
25-
| get metadata (stat) | | | | | | | |
25+
| get metadata (stat) | | | | | | | |
2626
| identity (same file) | || ||| | |
2727
| open | | | | | | |
2828
| read file (bytes) | || ||| | |

pkgs/io_file/lib/src/file_system.dart

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,97 @@ import 'package:meta/meta.dart' show sealed;
1111
// `dart:io` then change the doc strings to use reference syntax rather than
1212
// code syntax e.g. `PathExistsException` => [PathExistsException].
1313

14+
/// The type of a file system object, such as a file or directory.
15+
enum FileSystemType {
16+
/// A special block file (also called a block device).
17+
///
18+
/// Only exists on POSIX systems.
19+
block,
20+
21+
/// A file that represents a character device, such as a terminal or printer.
22+
character,
23+
24+
/// A container for other file system objects.
25+
directory,
26+
27+
/// A regular file.
28+
file,
29+
30+
/// A symbolic link.
31+
link,
32+
33+
/// A pipe, named pipe or FIFO.
34+
pipe,
35+
36+
/// A unix domain socket.
37+
///
38+
/// Only exists on POSIX systems.
39+
socket,
40+
41+
/// The type of the file could not be determined.
42+
unknown,
43+
}
44+
1445
/// Information about a directory, link, etc. stored in the [FileSystem].
1546
abstract interface class Metadata {
16-
// TODO(brianquinlan): Document all public fields.
47+
/// The type of the file system object.
48+
FileSystemType get type;
1749

50+
/// Whether the file system object is a regular file.
51+
///
52+
/// This will be `false` for some file system objects that can be read or
53+
/// written to, such as sockets, pipes, and character devices. The most
54+
/// reliable way to determine if a file system object can be read or written
55+
/// to is to attempt to open it.
56+
///
57+
/// At most one of [isDirectory], [isFile], or [isLink] will be `true`.
1858
bool get isFile;
59+
60+
/// Whether the file system object is a directory.
61+
///
62+
/// At most one of [isDirectory], [isFile], or [isLink] will be `true`.
1963
bool get isDirectory;
64+
65+
/// Whether the file system object is symbolic link.
66+
///
67+
/// At most one of [isDirectory], [isFile], or [isLink] will be `true`.
2068
bool get isLink;
69+
70+
/// Whether the file system object is visible to the user.
71+
///
72+
/// This will be `null` if the operating system does not support file system
73+
/// visibility. It will always be `null` on Android and Linux.
74+
bool? get isHidden;
75+
76+
/// The size of the file system object in bytes.
77+
///
78+
/// The `size` presented for file system objects other than regular files is
79+
/// platform-specific.
2180
int get size;
81+
82+
/// The time that the file system object was last accessed.
83+
///
84+
/// Access time is updated when the object is read or modified.
85+
///
86+
/// The resolution of the access time varies by platform and file system.
87+
/// For example, FAT has an access time resolution of one day and NTFS may
88+
/// delay updating the access time for up to one hour after the last access.
89+
DateTime get access;
90+
91+
/// The time that the file system object was created.
92+
///
93+
/// This will always be `null` on platforms that do not track file creation
94+
/// time. It will always be `null` on Android and Linux.
95+
///
96+
/// The resolution of the creation time varies by platform and file system.
97+
/// For example, FAT has a creation time resolution of 10 millseconds.
98+
DateTime? get creation;
99+
100+
/// The time that the file system object was last modified.
101+
///
102+
/// The resolution of the modification time varies by platform and file
103+
/// system. For example, FAT has a modification time resolution of 2 seconds.
104+
DateTime get modification;
22105
}
23106

24107
/// The modes in which a File can be written.
@@ -88,10 +171,19 @@ abstract class FileSystem {
88171
/// ```
89172
String createTemporaryDirectory({String? parent, String? prefix});
90173

174+
/// TODO(brianquinlan): Add an `exists` method that can determine if a file
175+
/// exists without mutating it on Windows (maybe using `FindFirstFile`?)
176+
91177
/// Metadata for the file system object at [path].
92178
///
93179
/// If `path` represents a symbolic link then metadata for the link is
94180
/// returned.
181+
///
182+
/// On Windows, asking for the metadata for a named pipe may cause the server
183+
/// to close it.
184+
///
185+
/// The most reliable way to determine if a file system object can be read or
186+
/// written to is to attempt to open it.
95187
Metadata metadata(String path);
96188

97189
/// Deletes the directory at the given path.

pkgs/io_file/lib/src/vm_posix_file_system.dart

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const _defaultMode = 438; // => 0666 => rw-rw-rw-
2121
/// The default `mode` to use when creating a directory.
2222
const _defaultDirectoryMode = 511; // => 0777 => rwxrwxrwx
2323

24+
const _nanosecondsPerSecond = 1000000000;
25+
2426
Exception _getError(int err, String message, String path) {
2527
//TODO(brianquinlan): In the long-term, do we need to avoid exceptions that
2628
// are part of `dart:io`? Can we move those exceptions into a different
@@ -50,6 +52,147 @@ int _tempFailureRetry(int Function() f) {
5052
return result;
5153
}
5254

55+
/// Information about a directory, link, etc. stored in the [PosixFileSystem].
56+
final class PosixMetadata implements Metadata {
57+
/// The `st_mode` field of the POSIX stat struct.
58+
///
59+
/// See [stat.h](https://pubs.opengroup.org/onlinepubs/009696799/basedefs/sys/stat.h.html)
60+
/// for information on how to interpret this field.
61+
final int mode;
62+
final int _flags;
63+
64+
@override
65+
final int size;
66+
67+
/// The time that the file system object was last accessed in nanoseconds
68+
/// since the epoch.
69+
///
70+
/// Access time is updated when the object is read or modified.
71+
///
72+
/// The resolution of the access time varies by platform and file system.
73+
final int accessedTimeNanos;
74+
75+
/// The time that the file system object was created in nanoseconds since the
76+
/// epoch.
77+
///
78+
/// This will always be `null` on Android and Linux.
79+
///
80+
/// The resolution of the creation time varies by platform and file system.
81+
final int? creationTimeNanos;
82+
83+
/// The time that the file system object was last modified in nanoseconds
84+
/// since the epoch.
85+
///
86+
/// The resolution of the modification time varies by platform and file
87+
/// system.
88+
final int modificationTimeNanos;
89+
90+
int get _fmt => mode & libc.S_IFMT;
91+
92+
@override
93+
FileSystemType get type {
94+
if (_fmt == libc.S_IFBLK) {
95+
return FileSystemType.block;
96+
}
97+
if (_fmt == libc.S_IFCHR) {
98+
return FileSystemType.character;
99+
}
100+
if (_fmt == libc.S_IFDIR) {
101+
return FileSystemType.directory;
102+
}
103+
if (_fmt == libc.S_IFREG) {
104+
return FileSystemType.file;
105+
}
106+
if (_fmt == libc.S_IFLNK) {
107+
return FileSystemType.link;
108+
}
109+
if (_fmt == libc.S_IFIFO) {
110+
return FileSystemType.pipe;
111+
}
112+
if (_fmt == libc.S_IFSOCK) {
113+
return FileSystemType.socket;
114+
}
115+
return FileSystemType.unknown;
116+
}
117+
118+
@override
119+
bool get isDirectory => type == FileSystemType.directory;
120+
121+
@override
122+
bool get isFile => type == FileSystemType.file;
123+
124+
@override
125+
bool get isLink => type == FileSystemType.link;
126+
127+
@override
128+
DateTime get access =>
129+
DateTime.fromMicrosecondsSinceEpoch(accessedTimeNanos ~/ 1000);
130+
131+
@override
132+
DateTime? get creation =>
133+
creationTimeNanos == null
134+
? null
135+
: DateTime.fromMicrosecondsSinceEpoch(creationTimeNanos! ~/ 1000);
136+
137+
@override
138+
DateTime get modification =>
139+
DateTime.fromMicrosecondsSinceEpoch(modificationTimeNanos ~/ 1000);
140+
141+
@override
142+
bool? get isHidden {
143+
if (io.Platform.isIOS || io.Platform.isMacOS) {
144+
return _flags & libc.UF_HIDDEN != 0;
145+
}
146+
return null;
147+
}
148+
149+
PosixMetadata._(
150+
this.mode,
151+
this._flags,
152+
this.size,
153+
this.accessedTimeNanos,
154+
this.creationTimeNanos,
155+
this.modificationTimeNanos,
156+
);
157+
158+
/// Construct [PosixMetadata] from data returned by the `stat` system call.
159+
factory PosixMetadata.fromFileAttributes({
160+
required int mode,
161+
int flags = 0,
162+
int size = 0,
163+
int accessedTimeNanos = 0,
164+
int? creationTimeNanos,
165+
int modificationTimeNanos = 0,
166+
}) => PosixMetadata._(
167+
mode,
168+
flags,
169+
size,
170+
accessedTimeNanos,
171+
creationTimeNanos,
172+
modificationTimeNanos,
173+
);
174+
175+
@override
176+
bool operator ==(Object other) =>
177+
other is PosixMetadata &&
178+
mode == other.mode &&
179+
_flags == other._flags &&
180+
size == other.size &&
181+
accessedTimeNanos == other.accessedTimeNanos &&
182+
creationTimeNanos == other.creationTimeNanos &&
183+
modificationTimeNanos == other.modificationTimeNanos;
184+
185+
@override
186+
int get hashCode => Object.hash(
187+
mode,
188+
_flags,
189+
size,
190+
accessedTimeNanos,
191+
creationTimeNanos,
192+
modificationTimeNanos,
193+
);
194+
}
195+
53196
/// The POSIX `read` function.
54197
///
55198
/// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
@@ -112,9 +255,29 @@ final class PosixFileSystem extends FileSystem {
112255
});
113256

114257
@override
115-
Metadata metadata(String path) {
116-
throw UnimplementedError();
117-
}
258+
PosixMetadata metadata(String path) => ffi.using((arena) {
259+
final stat = arena<libc.Stat>();
260+
261+
if (libc.lstat(path.toNativeUtf8(allocator: arena).cast(), stat) == -1) {
262+
final errno = libc.errno;
263+
throw _getError(errno, 'stat failed', path);
264+
}
265+
266+
return PosixMetadata.fromFileAttributes(
267+
mode: stat.ref.st_mode,
268+
flags: stat.ref.st_flags,
269+
size: stat.ref.st_size,
270+
accessedTimeNanos:
271+
stat.ref.st_atim.tv_sec * _nanosecondsPerSecond +
272+
stat.ref.st_atim.tv_sec,
273+
creationTimeNanos:
274+
stat.ref.st_btime.tv_sec * _nanosecondsPerSecond +
275+
stat.ref.st_btime.tv_sec,
276+
modificationTimeNanos:
277+
stat.ref.st_mtim.tv_sec * _nanosecondsPerSecond +
278+
stat.ref.st_mtim.tv_sec,
279+
);
280+
});
118281

119282
@override
120283
void removeDirectory(String path) => ffi.using((arena) {

0 commit comments

Comments
 (0)