diff --git a/cli/js/file_info.ts b/cli/js/file_info.ts index 2f08658c7a..f0ab5bf6ca 100644 --- a/cli/js/file_info.ts +++ b/cli/js/file_info.ts @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { StatResponse } from "./stat.ts"; +import { build } from "./build.ts"; /** A FileInfo describes a file and is returned by `stat`, `lstat`, * `statSync`, `lstatSync`. @@ -22,13 +23,38 @@ export interface FileInfo { * be available on all platforms. */ 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 * for this file/directory. TODO Match behavior with Go on windows for mode. */ mode: number | null; - /** The file or directory name. */ - name: string | null; + /** Number of hard links pointing to this file. Unix only. */ + 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 * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. @@ -54,17 +80,37 @@ export class FileInfoImpl implements FileInfo { modified: number | null; accessed: number | null; created: number | null; - mode: number | 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 */ constructor(private _res: StatResponse) { + const isUnix = build.os === "mac" || build.os === "linux"; const modified = this._res.modified; const accessed = this._res.accessed; 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; + // Unix only + const { + dev, + ino, + mode, + nlink, + uid, + gid, + rdev, + blksize, + blocks + } = this._res; this._isFile = this._res.isFile; this._isSymlink = this._res.isSymlink; @@ -72,9 +118,17 @@ export class FileInfoImpl implements FileInfo { this.modified = modified ? modified : null; this.accessed = accessed ? accessed : null; this.created = created ? created : null; - // null on Windows - this.mode = hasMode ? mode : 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 { diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts index f65c9650df..f95236af7b 100644 --- a/cli/js/lib.deno_runtime.d.ts +++ b/cli/js/lib.deno_runtime.d.ts @@ -732,12 +732,28 @@ declare namespace Deno { * be available on all platforms. */ 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 * for this file/directory. TODO Match behavior with Go on windows for mode. */ mode: number | null; - /** The file or directory name. */ - name: string | null; + /** Number of hard links pointing to this file. Unix only. */ + 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 * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. */ @@ -827,9 +843,16 @@ declare namespace Deno { modified: number; accessed: number; created: number; - mode: number; - hasMode: boolean; 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 * path is a symlink information about the symlink will be returned. diff --git a/cli/js/stat.ts b/cli/js/stat.ts index 4a07eca212..b271702636 100644 --- a/cli/js/stat.ts +++ b/cli/js/stat.ts @@ -10,9 +10,17 @@ export interface StatResponse { modified: number; accessed: number; created: number; - mode: number; - hasMode: boolean; // false on windows 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 diff --git a/cli/js/stat_test.ts b/cli/js/stat_test.ts index 9e78888a3c..7b4f06114b 100644 --- a/cli/js/stat_test.ts +++ b/cli/js/stat_test.ts @@ -170,3 +170,53 @@ testPerm({ read: true }, async function lstatNotFound(): Promise { assert(caughtError); assertEquals(badInfo, undefined); }); + +const isWindows = Deno.build.os === "win"; + +// OS dependent tests +if (isWindows) { + testPerm( + { read: true, write: true }, + async function statNoUnixFields(): Promise { + 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 { + 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); + } + ); +} diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index df15e2c889..c6830ddd2a 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -13,6 +13,8 @@ use std::fs; use std::path::PathBuf; use std::time::UNIX_EPOCH; +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -226,14 +228,55 @@ macro_rules! to_seconds { }}; } -#[cfg(any(unix))] -fn get_mode(perm: &fs::Permissions) -> u32 { - perm.mode() -} +#[inline(always)] +fn get_stat_json( + metadata: fs::Metadata, + maybe_name: Option, +) -> Result { + // 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)))] -fn get_mode(_perm: &fs::Permissions) -> u32 { - 0 + let mut json_val = json!({ + "isFile": metadata.is_file(), + "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)] @@ -265,17 +308,7 @@ fn op_stat( } else { fs::metadata(&filename)? }; - - 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, - })) + get_stat_json(metadata, None) }) } @@ -335,19 +368,11 @@ fn op_read_dir( .map(|entry| { let entry = entry.unwrap(); let metadata = entry.metadata().unwrap(); - let file_type = metadata.file_type(); - - json!({ - "isFile": file_type.is_file(), - "isSymlink": 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()), - "name": entry.file_name().to_str().unwrap(), - "hasMode": cfg!(target_family = "unix"), // false on windows, - }) + get_stat_json( + metadata, + Some(entry.file_name().to_str().unwrap().to_owned()), + ) + .unwrap() }) .collect();