1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

feat(fs): add more unix-only fields to FileInfo (#3680)

This commit is contained in:
Kevin (Kun) "Kassimo" Qian 2020-01-16 06:46:32 -08:00 committed by Ry Dahl
parent 5856d21a2e
commit 91757f63fd
5 changed files with 204 additions and 44 deletions

View file

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { StatResponse } from "./stat.ts"; import { StatResponse } from "./stat.ts";
import { build } from "./build.ts";
/** A FileInfo describes a file and is returned by `stat`, `lstat`, /** A FileInfo describes a file and is returned by `stat`, `lstat`,
* `statSync`, `lstatSync`. * `statSync`, `lstatSync`.
@ -22,13 +23,38 @@ export interface FileInfo {
* be available on all platforms. * be available on all platforms.
*/ */
created: number | null; created: number | null;
/** The file or directory name. */
name: string | null;
/** ID of the device containing the file. Unix only. */
dev: number | null;
/** Inode number. Unix only. */
ino: number | null;
/** The underlying raw st_mode bits that contain the standard Unix permissions /** The underlying raw st_mode bits that contain the standard Unix permissions
* for this file/directory. TODO Match behavior with Go on windows for mode. * for this file/directory. TODO Match behavior with Go on windows for mode.
*/ */
mode: number | null; mode: number | null;
/** The file or directory name. */ /** Number of hard links pointing to this file. Unix only. */
name: string | null; nlink: number | null;
/** User ID of the owner of this file. Unix only. */
uid: number | null;
/** User ID of the owner of this file. Unix only. */
gid: number | null;
/** Device ID of this file. Unix only. */
rdev: number | null;
/** Blocksize for filesystem I/O. Unix only. */
blksize: number | null;
/** Number of blocks allocated to the file, in 512-byte units. Unix only. */
blocks: number | null;
/** Returns whether this is info for a regular file. This result is mutually /** Returns whether this is info for a regular file. This result is mutually
* exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`.
@ -54,17 +80,37 @@ export class FileInfoImpl implements FileInfo {
modified: number | null; modified: number | null;
accessed: number | null; accessed: number | null;
created: number | null; created: number | null;
mode: number | null;
name: string | null; name: string | null;
dev: number | null;
ino: number | null;
mode: number | null;
nlink: number | null;
uid: number | null;
gid: number | null;
rdev: number | null;
blksize: number | null;
blocks: number | null;
/* @internal */ /* @internal */
constructor(private _res: StatResponse) { constructor(private _res: StatResponse) {
const isUnix = build.os === "mac" || build.os === "linux";
const modified = this._res.modified; const modified = this._res.modified;
const accessed = this._res.accessed; const accessed = this._res.accessed;
const created = this._res.created; const created = this._res.created;
const hasMode = this._res.hasMode;
const mode = this._res.mode; // negative for invalid mode (Windows)
const name = this._res.name; const name = this._res.name;
// Unix only
const {
dev,
ino,
mode,
nlink,
uid,
gid,
rdev,
blksize,
blocks
} = this._res;
this._isFile = this._res.isFile; this._isFile = this._res.isFile;
this._isSymlink = this._res.isSymlink; this._isSymlink = this._res.isSymlink;
@ -72,9 +118,17 @@ export class FileInfoImpl implements FileInfo {
this.modified = modified ? modified : null; this.modified = modified ? modified : null;
this.accessed = accessed ? accessed : null; this.accessed = accessed ? accessed : null;
this.created = created ? created : null; this.created = created ? created : null;
// null on Windows
this.mode = hasMode ? mode : null;
this.name = name ? name : null; this.name = name ? name : null;
// Only non-null if on Unix
this.dev = isUnix ? dev : null;
this.ino = isUnix ? ino : null;
this.mode = isUnix ? mode : null;
this.nlink = isUnix ? nlink : null;
this.uid = isUnix ? uid : null;
this.gid = isUnix ? gid : null;
this.rdev = isUnix ? rdev : null;
this.blksize = isUnix ? blksize : null;
this.blocks = isUnix ? blocks : null;
} }
isFile(): boolean { isFile(): boolean {

View file

@ -732,12 +732,28 @@ declare namespace Deno {
* be available on all platforms. * be available on all platforms.
*/ */
created: number | null; created: number | null;
/** The file or directory name. */
name: string | null;
/** ID of the device containing the file. Unix only. */
dev: number | null;
/** Inode number. Unix only. */
ino: number | null;
/** The underlying raw st_mode bits that contain the standard Unix permissions /** The underlying raw st_mode bits that contain the standard Unix permissions
* for this file/directory. TODO Match behavior with Go on windows for mode. * for this file/directory. TODO Match behavior with Go on windows for mode.
*/ */
mode: number | null; mode: number | null;
/** The file or directory name. */ /** Number of hard links pointing to this file. Unix only. */
name: string | null; nlink: number | null;
/** User ID of the owner of this file. Unix only. */
uid: number | null;
/** User ID of the owner of this file. Unix only. */
gid: number | null;
/** Device ID of this file. Unix only. */
rdev: number | null;
/** Blocksize for filesystem I/O. Unix only. */
blksize: number | null;
/** Number of blocks allocated to the file, in 512-byte units. Unix only. */
blocks: number | null;
/** Returns whether this is info for a regular file. This result is mutually /** Returns whether this is info for a regular file. This result is mutually
* exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`.
*/ */
@ -827,9 +843,16 @@ declare namespace Deno {
modified: number; modified: number;
accessed: number; accessed: number;
created: number; created: number;
mode: number;
hasMode: boolean;
name: string | null; name: string | null;
dev: number;
ino: number;
mode: number;
nlink: number;
uid: number;
gid: number;
rdev: number;
blksize: number;
blocks: number;
} }
/** Queries the file system for information on the path provided. If the given /** Queries the file system for information on the path provided. If the given
* path is a symlink information about the symlink will be returned. * path is a symlink information about the symlink will be returned.

View file

@ -10,9 +10,17 @@ export interface StatResponse {
modified: number; modified: number;
accessed: number; accessed: number;
created: number; created: number;
mode: number;
hasMode: boolean; // false on windows
name: string | null; name: string | null;
// Unix only members
dev: number;
ino: number;
mode: number;
nlink: number;
uid: number;
gid: number;
rdev: number;
blksize: number;
blocks: number;
} }
/** Queries the file system for information on the path provided. If the given /** Queries the file system for information on the path provided. If the given

View file

@ -170,3 +170,53 @@ testPerm({ read: true }, async function lstatNotFound(): Promise<void> {
assert(caughtError); assert(caughtError);
assertEquals(badInfo, undefined); assertEquals(badInfo, undefined);
}); });
const isWindows = Deno.build.os === "win";
// OS dependent tests
if (isWindows) {
testPerm(
{ read: true, write: true },
async function statNoUnixFields(): Promise<void> {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const filename = tempDir + "/test.txt";
Deno.writeFileSync(filename, data, { perm: 0o666 });
const s = Deno.statSync(filename);
assert(s.dev === null);
assert(s.ino === null);
assert(s.mode === null);
assert(s.nlink === null);
assert(s.uid === null);
assert(s.gid === null);
assert(s.rdev === null);
assert(s.blksize === null);
assert(s.blocks === null);
}
);
} else {
testPerm(
{ read: true, write: true },
async function statUnixFields(): Promise<void> {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const filename = tempDir + "/test.txt";
const filename2 = tempDir + "/test2.txt";
Deno.writeFileSync(filename, data, { perm: 0o666 });
// Create a link
Deno.linkSync(filename, filename2);
const s = Deno.statSync(filename);
assert(s.dev !== null);
assert(s.ino !== null);
assertEquals(s.mode & 0o666, 0o666);
assertEquals(s.nlink, 2);
assert(s.uid !== null);
assert(s.gid !== null);
assert(s.rdev !== null);
assert(s.blksize !== null);
assert(s.blocks !== null);
}
);
}

View file

@ -13,6 +13,8 @@ use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@ -226,14 +228,55 @@ macro_rules! to_seconds {
}}; }};
} }
#[cfg(any(unix))] #[inline(always)]
fn get_mode(perm: &fs::Permissions) -> u32 { fn get_stat_json(
perm.mode() metadata: fs::Metadata,
} maybe_name: Option<String>,
) -> Result<Value, ErrBox> {
// Unix stat member (number types only). 0 if not on unix.
macro_rules! usm {
($member: ident) => {{
#[cfg(unix)]
{
metadata.$member()
}
#[cfg(not(unix))]
{
0
}
}};
}
#[cfg(not(any(unix)))] let mut json_val = json!({
fn get_mode(_perm: &fs::Permissions) -> u32 { "isFile": metadata.is_file(),
0 "isSymlink": metadata.file_type().is_symlink(),
"len": metadata.len(),
// In seconds. Available on both Unix or Windows.
"modified":to_seconds!(metadata.modified()),
"accessed":to_seconds!(metadata.accessed()),
"created":to_seconds!(metadata.created()),
// Following are only valid under Unix.
"dev": usm!(dev),
"ino": usm!(ino),
"mode": usm!(mode),
"nlink": usm!(nlink),
"uid": usm!(uid),
"gid": usm!(gid),
"rdev": usm!(rdev),
// TODO(kevinkassimo): *time_nsec requires BigInt.
// Probably should be treated as String if we need to add them.
"blksize": usm!(blksize),
"blocks": usm!(blocks),
});
// "name" is an optional field by our design.
if let Some(name) = maybe_name {
if let serde_json::Value::Object(ref mut m) = json_val {
m.insert("name".to_owned(), json!(name));
}
}
Ok(json_val)
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -265,17 +308,7 @@ fn op_stat(
} else { } else {
fs::metadata(&filename)? fs::metadata(&filename)?
}; };
get_stat_json(metadata, None)
Ok(json!({
"isFile": metadata.is_file(),
"isSymlink": metadata.file_type().is_symlink(),
"len": metadata.len(),
"modified":to_seconds!(metadata.modified()),
"accessed":to_seconds!(metadata.accessed()),
"created":to_seconds!(metadata.created()),
"mode": get_mode(&metadata.permissions()),
"hasMode": cfg!(target_family = "unix"), // false on windows,
}))
}) })
} }
@ -335,19 +368,11 @@ fn op_read_dir(
.map(|entry| { .map(|entry| {
let entry = entry.unwrap(); let entry = entry.unwrap();
let metadata = entry.metadata().unwrap(); let metadata = entry.metadata().unwrap();
let file_type = metadata.file_type(); get_stat_json(
metadata,
json!({ Some(entry.file_name().to_str().unwrap().to_owned()),
"isFile": file_type.is_file(), )
"isSymlink": file_type.is_symlink(), .unwrap()
"len": metadata.len(),
"modified": to_seconds!(metadata.modified()),
"accessed": to_seconds!(metadata.accessed()),
"created": to_seconds!(metadata.created()),
"mode": get_mode(&metadata.permissions()),
"name": entry.file_name().to_str().unwrap(),
"hasMode": cfg!(target_family = "unix"), // false on windows,
})
}) })
.collect(); .collect();