mirror of https://github.com/status-im/metro.git
metro-memory-fs: add some very basic 'mode' handling
Reviewed By: rafeca Differential Revision: D7890886 fbshipit-source-id: 2c9cfac43b745614cdf149ed242a324fc45077b1
This commit is contained in:
parent
bfcf4e32d9
commit
de68d129f7
|
@ -1,5 +1,9 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`posix support accessSync check owned file cannot be read 1`] = `"EPERM: \`/foo.txt\`: file cannot be read"`;
|
||||||
|
|
||||||
|
exports[`posix support accessSync check owned file cannot be written to 1`] = `"EPERM: \`/foo.txt\`: file cannot be written to"`;
|
||||||
|
|
||||||
exports[`posix support readlink throws when trying to read a regular file 1`] = `"EINVAL: \`/foo.txt\`: entity is not a symlink"`;
|
exports[`posix support readlink throws when trying to read a regular file 1`] = `"EINVAL: \`/foo.txt\`: entity is not a symlink"`;
|
||||||
|
|
||||||
exports[`posix support throws when finding a symlink loop 1`] = `"ELOOP: \`/foo.txt\`: too many levels of symbolic links"`;
|
exports[`posix support throws when finding a symlink loop 1`] = `"ELOOP: \`/foo.txt\`: too many levels of symbolic links"`;
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
|
||||||
const MemoryFs = require('../index');
|
const MemoryFs = require('../index');
|
||||||
|
@ -22,6 +24,32 @@ describe('posix support', () => {
|
||||||
fs = new MemoryFs();
|
fs = new MemoryFs();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('accessSync', () => {
|
||||||
|
it('accesses owned file', () => {
|
||||||
|
fs.writeFileSync('/foo.txt', 'test');
|
||||||
|
fs.accessSync('/foo.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check owned file can be read and written to', () => {
|
||||||
|
fs.writeFileSync('/foo.txt', 'test');
|
||||||
|
fs.accessSync('/foo.txt', fs.constants.R_OK | fs.constants.W_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check owned file cannot be read', () => {
|
||||||
|
fs.writeFileSync('/foo.txt', 'test', {mode: 0o000});
|
||||||
|
expectFsError('EPERM', () =>
|
||||||
|
fs.accessSync('/foo.txt', fs.constants.R_OK),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check owned file cannot be written to', () => {
|
||||||
|
fs.writeFileSync('/foo.txt', 'test', {mode: 0o000});
|
||||||
|
expectFsError('EPERM', () =>
|
||||||
|
fs.accessSync('/foo.txt', fs.constants.W_OK),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('can write then read a file', () => {
|
it('can write then read a file', () => {
|
||||||
fs.writeFileSync('/foo.txt', 'test');
|
fs.writeFileSync('/foo.txt', 'test');
|
||||||
expect(fs.readFileSync('/foo.txt', 'utf8')).toEqual('test');
|
expect(fs.readFileSync('/foo.txt', 'utf8')).toEqual('test');
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
|
||||||
// $FlowFixMe: not defined by Flow
|
// $FlowFixMe: not defined by Flow
|
||||||
const constants = require('constants');
|
const constants = require('constants');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
@ -17,7 +19,10 @@ const stream = require('stream');
|
||||||
const {EventEmitter} = require('events');
|
const {EventEmitter} = require('events');
|
||||||
|
|
||||||
type NodeBase = {|
|
type NodeBase = {|
|
||||||
|
gid: number,
|
||||||
id: number,
|
id: number,
|
||||||
|
mode: number,
|
||||||
|
uid: number,
|
||||||
watchers: Array<NodeWatcher>,
|
watchers: Array<NodeWatcher>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
@ -74,6 +79,8 @@ type Descriptor = {|
|
||||||
position: number,
|
position: number,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
type FilePath = string | Buffer;
|
||||||
|
|
||||||
const FLAGS_SPECS: {
|
const FLAGS_SPECS: {
|
||||||
[string]: {
|
[string]: {
|
||||||
exclusive?: true,
|
exclusive?: true,
|
||||||
|
@ -93,6 +100,7 @@ const FLAGS_SPECS: {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ASYNC_FUNC_NAMES = [
|
const ASYNC_FUNC_NAMES = [
|
||||||
|
'access',
|
||||||
'close',
|
'close',
|
||||||
'fstat',
|
'fstat',
|
||||||
'lstat',
|
'lstat',
|
||||||
|
@ -120,10 +128,11 @@ class MemoryFs {
|
||||||
_nextId: number;
|
_nextId: number;
|
||||||
_platform: 'win32' | 'posix';
|
_platform: 'win32' | 'posix';
|
||||||
_pathSep: string;
|
_pathSep: string;
|
||||||
|
constants = constants;
|
||||||
|
|
||||||
close: (fd: number, callback: (error: ?Error) => mixed) => void;
|
close: (fd: number, callback: (error: ?Error) => mixed) => void;
|
||||||
open: (
|
open: (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
flag: string | number,
|
flag: string | number,
|
||||||
mode?: number,
|
mode?: number,
|
||||||
callback: (error: ?Error, fd: ?number) => mixed,
|
callback: (error: ?Error, fd: ?number) => mixed,
|
||||||
|
@ -137,7 +146,7 @@ class MemoryFs {
|
||||||
callback: (?Error, ?number) => mixed,
|
callback: (?Error, ?number) => mixed,
|
||||||
) => void;
|
) => void;
|
||||||
readFile: (
|
readFile: (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
encoding?: Encoding,
|
encoding?: Encoding,
|
||||||
|
@ -147,10 +156,7 @@ class MemoryFs {
|
||||||
| ((?Error, ?Buffer | string) => mixed),
|
| ((?Error, ?Buffer | string) => mixed),
|
||||||
callback?: (?Error, ?Buffer | string) => mixed,
|
callback?: (?Error, ?Buffer | string) => mixed,
|
||||||
) => void;
|
) => void;
|
||||||
realpath: (
|
realpath: (filePath: FilePath, callback: (?Error, ?string) => mixed) => void;
|
||||||
filePath: string | Buffer,
|
|
||||||
callback: (?Error, ?string) => mixed,
|
|
||||||
) => void;
|
|
||||||
write: (
|
write: (
|
||||||
fd: number,
|
fd: number,
|
||||||
bufferOrString: Buffer | string,
|
bufferOrString: Buffer | string,
|
||||||
|
@ -160,7 +166,7 @@ class MemoryFs {
|
||||||
callback?: (?Error, number) => mixed,
|
callback?: (?Error, number) => mixed,
|
||||||
) => void;
|
) => void;
|
||||||
writeFile: (
|
writeFile: (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
data: Buffer | string,
|
data: Buffer | string,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
|
@ -199,13 +205,57 @@ class MemoryFs {
|
||||||
this._nextId = 1;
|
this._nextId = 1;
|
||||||
this._roots = new Map();
|
this._roots = new Map();
|
||||||
if (this._platform === 'posix') {
|
if (this._platform === 'posix') {
|
||||||
this._roots.set('', this._makeDir());
|
this._roots.set('', this._makeDir(0o777));
|
||||||
} else if (this._platform === 'win32') {
|
} else if (this._platform === 'win32') {
|
||||||
this._roots.set('C:', this._makeDir());
|
this._roots.set('C:', this._makeDir(0o777));
|
||||||
}
|
}
|
||||||
this._fds = new Map();
|
this._fds = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessSync = (filePath: FilePath, mode?: number): void => {
|
||||||
|
if (mode == null) {
|
||||||
|
mode = constants.F_OK;
|
||||||
|
}
|
||||||
|
const stats = this.statSync(filePath);
|
||||||
|
if (mode == constants.F_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filePathStr = pathStr(filePath);
|
||||||
|
if ((mode & constants.R_OK) !== 0) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
(stats.mode & constants.S_IROTH) !== 0 ||
|
||||||
|
((stats.mode & constants.S_IRGRP) !== 0 && stats.gid === getgid()) ||
|
||||||
|
((stats.mode & constants.S_IRUSR) !== 0 && stats.uid === getuid())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw makeError('EPERM', filePathStr, 'file cannot be read');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((mode & constants.W_OK) !== 0) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
(stats.mode & constants.S_IWOTH) !== 0 ||
|
||||||
|
((stats.mode & constants.S_IWGRP) !== 0 && stats.gid === getgid()) ||
|
||||||
|
((stats.mode & constants.S_IWUSR) !== 0 && stats.uid === getuid())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw makeError('EPERM', filePathStr, 'file cannot be written to');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((mode & constants.X_OK) !== 0) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
(stats.mode & constants.S_IXOTH) !== 0 ||
|
||||||
|
((stats.mode & constants.S_IXGRP) !== 0 && stats.gid === getgid()) ||
|
||||||
|
((stats.mode & constants.S_IXUSR) !== 0 && stats.uid === getuid())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw makeError('EPERM', filePathStr, 'file cannot be executed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
closeSync = (fd: number): void => {
|
closeSync = (fd: number): void => {
|
||||||
const desc = this._getDesc(fd);
|
const desc = this._getDesc(fd);
|
||||||
if (desc.writable) {
|
if (desc.writable) {
|
||||||
|
@ -215,7 +265,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
openSync = (
|
openSync = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
flags: string | number,
|
flags: string | number,
|
||||||
mode?: number,
|
mode?: number,
|
||||||
): number => {
|
): number => {
|
||||||
|
@ -247,7 +297,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
readdirSync = (
|
readdirSync = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
encoding?: Encoding,
|
encoding?: Encoding,
|
||||||
|
@ -281,7 +331,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
readFileSync = (
|
readFileSync = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
encoding?: Encoding,
|
encoding?: Encoding,
|
||||||
|
@ -320,7 +370,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
readlinkSync = (
|
readlinkSync = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options: ?Encoding | {encoding: ?Encoding},
|
options: ?Encoding | {encoding: ?Encoding},
|
||||||
): string | Buffer => {
|
): string | Buffer => {
|
||||||
let encoding;
|
let encoding;
|
||||||
|
@ -347,7 +397,7 @@ class MemoryFs {
|
||||||
return buf.toString(encoding);
|
return buf.toString(encoding);
|
||||||
};
|
};
|
||||||
|
|
||||||
realpathSync = (filePath: string | Buffer): string => {
|
realpathSync = (filePath: FilePath): string => {
|
||||||
return this._resolve(pathStr(filePath)).realpath;
|
return this._resolve(pathStr(filePath)).realpath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -384,7 +434,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
writeFileSync = (
|
writeFileSync = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
data: Buffer | string,
|
data: Buffer | string,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
|
@ -423,12 +473,12 @@ class MemoryFs {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
throw makeError('EEXIST', dirPath, 'directory or file already exists');
|
throw makeError('EEXIST', dirPath, 'directory or file already exists');
|
||||||
}
|
}
|
||||||
dirNode.entries.set(basename, this._makeDir());
|
dirNode.entries.set(basename, this._makeDir(mode));
|
||||||
};
|
};
|
||||||
|
|
||||||
symlinkSync = (
|
symlinkSync = (
|
||||||
target: string | Buffer,
|
target: string | Buffer,
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
type?: string,
|
type?: string,
|
||||||
) => {
|
) => {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
|
@ -444,13 +494,16 @@ class MemoryFs {
|
||||||
}
|
}
|
||||||
dirNode.entries.set(basename, {
|
dirNode.entries.set(basename, {
|
||||||
id: this._getId(),
|
id: this._getId(),
|
||||||
|
gid: getgid(),
|
||||||
target: pathStr(target),
|
target: pathStr(target),
|
||||||
|
mode: 0o666,
|
||||||
|
uid: getuid(),
|
||||||
type: 'symbolicLink',
|
type: 'symbolicLink',
|
||||||
watchers: [],
|
watchers: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
existsSync = (filePath: string | Buffer): boolean => {
|
existsSync = (filePath: FilePath): boolean => {
|
||||||
try {
|
try {
|
||||||
const {node} = this._resolve(pathStr(filePath));
|
const {node} = this._resolve(pathStr(filePath));
|
||||||
return node != null;
|
return node != null;
|
||||||
|
@ -462,7 +515,7 @@ class MemoryFs {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
statSync = (filePath: string | Buffer) => {
|
statSync = (filePath: FilePath) => {
|
||||||
filePath = pathStr(filePath);
|
filePath = pathStr(filePath);
|
||||||
const {node} = this._resolve(filePath);
|
const {node} = this._resolve(filePath);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
|
@ -471,7 +524,7 @@ class MemoryFs {
|
||||||
return new Stats(node);
|
return new Stats(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
lstatSync = (filePath: string | Buffer) => {
|
lstatSync = (filePath: FilePath) => {
|
||||||
filePath = pathStr(filePath);
|
filePath = pathStr(filePath);
|
||||||
const {node} = this._resolve(filePath, {
|
const {node} = this._resolve(filePath, {
|
||||||
keepFinalSymlink: true,
|
keepFinalSymlink: true,
|
||||||
|
@ -488,7 +541,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
createReadStream = (
|
createReadStream = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
autoClose?: ?boolean,
|
autoClose?: ?boolean,
|
||||||
|
@ -530,7 +583,7 @@ class MemoryFs {
|
||||||
return rst;
|
return rst;
|
||||||
};
|
};
|
||||||
|
|
||||||
unlinkSync = (filePath: string | Buffer) => {
|
unlinkSync = (filePath: FilePath) => {
|
||||||
filePath = pathStr(filePath);
|
filePath = pathStr(filePath);
|
||||||
const {basename, dirNode, dirPath, node} = this._resolve(filePath, {
|
const {basename, dirNode, dirPath, node} = this._resolve(filePath, {
|
||||||
keepFinalSymlink: true,
|
keepFinalSymlink: true,
|
||||||
|
@ -548,7 +601,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
createWriteStream = (
|
createWriteStream = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
autoClose?: ?boolean,
|
autoClose?: ?boolean,
|
||||||
|
@ -585,7 +638,7 @@ class MemoryFs {
|
||||||
};
|
};
|
||||||
|
|
||||||
watch = (
|
watch = (
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
encoding?: Encoding,
|
encoding?: Encoding,
|
||||||
|
@ -620,10 +673,13 @@ class MemoryFs {
|
||||||
return watcher;
|
return watcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
_makeDir() {
|
_makeDir(mode: number): DirectoryNode {
|
||||||
return {
|
return {
|
||||||
entries: new Map(),
|
entries: new Map(),
|
||||||
|
gid: getgid(),
|
||||||
id: this._getId(),
|
id: this._getId(),
|
||||||
|
mode,
|
||||||
|
uid: getuid(),
|
||||||
type: 'directory',
|
type: 'directory',
|
||||||
watchers: [],
|
watchers: [],
|
||||||
};
|
};
|
||||||
|
@ -651,7 +707,10 @@ class MemoryFs {
|
||||||
}
|
}
|
||||||
node = {
|
node = {
|
||||||
content: new Buffer(0),
|
content: new Buffer(0),
|
||||||
|
gid: getgid(),
|
||||||
id: this._getId(),
|
id: this._getId(),
|
||||||
|
mode,
|
||||||
|
uid: getuid(),
|
||||||
type: 'file',
|
type: 'file',
|
||||||
watchers: [],
|
watchers: [],
|
||||||
};
|
};
|
||||||
|
@ -919,10 +978,10 @@ class Stats {
|
||||||
constructor(node: EntityNode) {
|
constructor(node: EntityNode) {
|
||||||
this._type = node.type;
|
this._type = node.type;
|
||||||
this.dev = 1;
|
this.dev = 1;
|
||||||
this.mode = 0;
|
this.mode = node.mode;
|
||||||
this.nlink = 1;
|
this.nlink = 1;
|
||||||
this.uid = 100;
|
this.uid = node.uid;
|
||||||
this.gid = 100;
|
this.gid = node.gid;
|
||||||
this.rdev = 0;
|
this.rdev = 0;
|
||||||
this.blksize = 1024;
|
this.blksize = 1024;
|
||||||
this.ino = node.id;
|
this.ino = node.id;
|
||||||
|
@ -983,7 +1042,7 @@ class ReadFileSteam extends stream.Readable {
|
||||||
path: string | Buffer;
|
path: string | Buffer;
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
encoding: ?Encoding,
|
encoding: ?Encoding,
|
||||||
end: ?number,
|
end: ?number,
|
||||||
fd: number,
|
fd: number,
|
||||||
|
@ -1049,7 +1108,7 @@ class WriteFileStream extends stream.Writable {
|
||||||
|
|
||||||
constructor(opts: {
|
constructor(opts: {
|
||||||
fd: number,
|
fd: number,
|
||||||
filePath: string | Buffer,
|
filePath: FilePath,
|
||||||
writeSync: WriteSync,
|
writeSync: WriteSync,
|
||||||
start: ?number,
|
start: ?number,
|
||||||
}) {
|
}) {
|
||||||
|
@ -1126,7 +1185,7 @@ function checkPathLength(entNames, filePath) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pathStr(filePath: string | Buffer): string {
|
function pathStr(filePath: FilePath): string {
|
||||||
if (typeof filePath === 'string') {
|
if (typeof filePath === 'string') {
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
@ -1152,4 +1211,12 @@ function nullthrows<T>(x: ?T): T {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getgid(): number {
|
||||||
|
return process.getgid != null ? process.getgid() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getuid(): number {
|
||||||
|
return process.getuid != null ? process.getuid() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = MemoryFs;
|
module.exports = MemoryFs;
|
||||||
|
|
Loading…
Reference in New Issue