@zenfs/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -0
- package/dist/ApiError.d.ts +86 -0
- package/dist/ApiError.js +135 -0
- package/dist/backends/AsyncMirror.d.ts +102 -0
- package/dist/backends/AsyncMirror.js +252 -0
- package/dist/backends/AsyncStore.d.ts +166 -0
- package/dist/backends/AsyncStore.js +620 -0
- package/dist/backends/FolderAdapter.d.ts +52 -0
- package/dist/backends/FolderAdapter.js +184 -0
- package/dist/backends/InMemory.d.ts +25 -0
- package/dist/backends/InMemory.js +46 -0
- package/dist/backends/Locked.d.ts +64 -0
- package/dist/backends/Locked.js +302 -0
- package/dist/backends/OverlayFS.d.ts +120 -0
- package/dist/backends/OverlayFS.js +749 -0
- package/dist/backends/SyncStore.d.ts +223 -0
- package/dist/backends/SyncStore.js +479 -0
- package/dist/backends/backend.d.ts +73 -0
- package/dist/backends/backend.js +14 -0
- package/dist/backends/index.d.ts +11 -0
- package/dist/backends/index.js +15 -0
- package/dist/browser.min.js +12 -0
- package/dist/browser.min.js.map +7 -0
- package/dist/cred.d.ts +14 -0
- package/dist/cred.js +15 -0
- package/dist/emulation/callbacks.d.ts +382 -0
- package/dist/emulation/callbacks.js +422 -0
- package/dist/emulation/constants.d.ts +101 -0
- package/dist/emulation/constants.js +110 -0
- package/dist/emulation/fs.d.ts +7 -0
- package/dist/emulation/fs.js +5 -0
- package/dist/emulation/index.d.ts +5 -0
- package/dist/emulation/index.js +7 -0
- package/dist/emulation/promises.d.ts +309 -0
- package/dist/emulation/promises.js +521 -0
- package/dist/emulation/shared.d.ts +62 -0
- package/dist/emulation/shared.js +192 -0
- package/dist/emulation/sync.d.ts +278 -0
- package/dist/emulation/sync.js +392 -0
- package/dist/file.d.ts +449 -0
- package/dist/file.js +576 -0
- package/dist/filesystem.d.ts +367 -0
- package/dist/filesystem.js +542 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +113 -0
- package/dist/inode.d.ts +51 -0
- package/dist/inode.js +112 -0
- package/dist/mutex.d.ts +12 -0
- package/dist/mutex.js +48 -0
- package/dist/stats.d.ts +98 -0
- package/dist/stats.js +226 -0
- package/dist/utils.d.ts +52 -0
- package/dist/utils.js +261 -0
- package/license.md +122 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZenFS's main module. This is exposed in the browser via the ZenFS global.
|
|
3
|
+
*/
|
|
4
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
import fs from './emulation/fs';
|
|
14
|
+
import { FileSystem } from './filesystem';
|
|
15
|
+
import { backends } from './backends';
|
|
16
|
+
import { ErrorCode, ApiError } from './ApiError';
|
|
17
|
+
import { Cred } from './cred';
|
|
18
|
+
import * as process from 'process';
|
|
19
|
+
import { setCred } from './emulation/shared';
|
|
20
|
+
if (process && process['initializeTTYs']) {
|
|
21
|
+
process['initializeTTYs']();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initializes ZenFS with the given file systems.
|
|
25
|
+
*/
|
|
26
|
+
export function initialize(mounts, uid = 0, gid = 0) {
|
|
27
|
+
setCred(new Cred(uid, gid, uid, gid, uid, gid));
|
|
28
|
+
return fs.initialize(mounts);
|
|
29
|
+
}
|
|
30
|
+
function _configure(config) {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
if ('fs' in config || config instanceof FileSystem) {
|
|
33
|
+
// single FS
|
|
34
|
+
config = { '/': config };
|
|
35
|
+
}
|
|
36
|
+
for (let [point, value] of Object.entries(config)) {
|
|
37
|
+
if (typeof value == 'number') {
|
|
38
|
+
//should never happen
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
point = point.toString(); // so linting stops complaining that point should be declared with const, which can't be done since value is assigned to
|
|
42
|
+
if (value instanceof FileSystem) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value == 'string') {
|
|
46
|
+
value = { fs: value };
|
|
47
|
+
}
|
|
48
|
+
config[point] = yield getFileSystem(value);
|
|
49
|
+
}
|
|
50
|
+
return initialize(config);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function configure(config, cb) {
|
|
54
|
+
// Promise version
|
|
55
|
+
if (typeof cb != 'function') {
|
|
56
|
+
return _configure(config);
|
|
57
|
+
}
|
|
58
|
+
// Callback version
|
|
59
|
+
_configure(config)
|
|
60
|
+
.then(() => cb())
|
|
61
|
+
.catch(err => cb(err));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
function _getFileSystem({ fs: fsName, options = {} }) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
if (!fsName) {
|
|
67
|
+
throw new ApiError(ErrorCode.EPERM, 'Missing "fs" property on configuration object.');
|
|
68
|
+
}
|
|
69
|
+
if (typeof options !== 'object' || options === null) {
|
|
70
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid "options" property on configuration object.');
|
|
71
|
+
}
|
|
72
|
+
const props = Object.keys(options).filter(k => k != 'fs');
|
|
73
|
+
for (const prop of props) {
|
|
74
|
+
const opt = options[prop];
|
|
75
|
+
if (opt === null || typeof opt !== 'object' || !('fs' in opt)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const fs = yield _getFileSystem(opt);
|
|
79
|
+
options[prop] = fs;
|
|
80
|
+
}
|
|
81
|
+
const fsc = backends[fsName];
|
|
82
|
+
if (!fsc) {
|
|
83
|
+
throw new ApiError(ErrorCode.EPERM, `File system ${fsName} is not available in ZenFS.`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return fsc.Create(options);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function getFileSystem(config, cb) {
|
|
91
|
+
// Promise version
|
|
92
|
+
if (typeof cb != 'function') {
|
|
93
|
+
return _getFileSystem(config);
|
|
94
|
+
}
|
|
95
|
+
// Callback version
|
|
96
|
+
_getFileSystem(config)
|
|
97
|
+
.then(fs => cb(null, fs))
|
|
98
|
+
.catch(err => cb(err));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
export * from './backends';
|
|
102
|
+
export * from './backends/AsyncStore';
|
|
103
|
+
export * from './backends/SyncStore';
|
|
104
|
+
export * from './ApiError';
|
|
105
|
+
export * from './cred';
|
|
106
|
+
export * from './file';
|
|
107
|
+
export * from './filesystem';
|
|
108
|
+
export * from './inode';
|
|
109
|
+
export * from './mutex';
|
|
110
|
+
export * from './stats';
|
|
111
|
+
export * from './utils';
|
|
112
|
+
export { fs };
|
|
113
|
+
export default fs;
|
package/dist/inode.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Stats } from './stats';
|
|
3
|
+
/**
|
|
4
|
+
* Generic inode definition that can easily be serialized.
|
|
5
|
+
*/
|
|
6
|
+
export default class Inode {
|
|
7
|
+
id: string;
|
|
8
|
+
size: number;
|
|
9
|
+
mode: number;
|
|
10
|
+
atime: number;
|
|
11
|
+
mtime: number;
|
|
12
|
+
ctime: number;
|
|
13
|
+
uid: number;
|
|
14
|
+
gid: number;
|
|
15
|
+
/**
|
|
16
|
+
* Converts the buffer into an Inode.
|
|
17
|
+
*/
|
|
18
|
+
static fromBuffer(buffer: Buffer): Inode;
|
|
19
|
+
constructor(id: string, size: number, mode: number, atime: number, mtime: number, ctime: number, uid: number, gid: number);
|
|
20
|
+
/**
|
|
21
|
+
* Handy function that converts the Inode to a Node Stats object.
|
|
22
|
+
*/
|
|
23
|
+
toStats(): Stats;
|
|
24
|
+
/**
|
|
25
|
+
* Get the size of this Inode, in bytes.
|
|
26
|
+
*/
|
|
27
|
+
getSize(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Writes the inode into the start of the buffer.
|
|
30
|
+
*/
|
|
31
|
+
toBuffer(buff?: Buffer): Buffer;
|
|
32
|
+
/**
|
|
33
|
+
* Updates the Inode using information from the stats object. Used by file
|
|
34
|
+
* systems at sync time, e.g.:
|
|
35
|
+
* - Program opens file and gets a File object.
|
|
36
|
+
* - Program mutates file. File object is responsible for maintaining
|
|
37
|
+
* metadata changes locally -- typically in a Stats object.
|
|
38
|
+
* - Program closes file. File object's metadata changes are synced with the
|
|
39
|
+
* file system.
|
|
40
|
+
* @return True if any changes have occurred.
|
|
41
|
+
*/
|
|
42
|
+
update(stats: Stats): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* @return [Boolean] True if this item is a file.
|
|
45
|
+
*/
|
|
46
|
+
isFile(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* @return [Boolean] True if this item is a directory.
|
|
49
|
+
*/
|
|
50
|
+
isDirectory(): boolean;
|
|
51
|
+
}
|
package/dist/inode.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Stats, FileType } from './stats';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
/**
|
|
4
|
+
* Generic inode definition that can easily be serialized.
|
|
5
|
+
*/
|
|
6
|
+
export default class Inode {
|
|
7
|
+
/**
|
|
8
|
+
* Converts the buffer into an Inode.
|
|
9
|
+
*/
|
|
10
|
+
static fromBuffer(buffer) {
|
|
11
|
+
if (buffer === undefined) {
|
|
12
|
+
throw new Error('NO');
|
|
13
|
+
}
|
|
14
|
+
return new Inode(buffer.toString('ascii', 38), buffer.readUInt32LE(0), buffer.readUInt16LE(4), buffer.readDoubleLE(6), buffer.readDoubleLE(14), buffer.readDoubleLE(22), buffer.readUInt32LE(30), buffer.readUInt32LE(34));
|
|
15
|
+
}
|
|
16
|
+
constructor(id, size, mode, atime, mtime, ctime, uid, gid) {
|
|
17
|
+
this.id = id;
|
|
18
|
+
this.size = size;
|
|
19
|
+
this.mode = mode;
|
|
20
|
+
this.atime = atime;
|
|
21
|
+
this.mtime = mtime;
|
|
22
|
+
this.ctime = ctime;
|
|
23
|
+
this.uid = uid;
|
|
24
|
+
this.gid = gid;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handy function that converts the Inode to a Node Stats object.
|
|
28
|
+
*/
|
|
29
|
+
toStats() {
|
|
30
|
+
return new Stats((this.mode & 0xf000) === FileType.DIRECTORY ? FileType.DIRECTORY : FileType.FILE, this.size, this.mode, this.atime, this.mtime, this.ctime, this.uid, this.gid);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the size of this Inode, in bytes.
|
|
34
|
+
*/
|
|
35
|
+
getSize() {
|
|
36
|
+
// ASSUMPTION: ID is ASCII (1 byte per char).
|
|
37
|
+
return 38 + this.id.length;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Writes the inode into the start of the buffer.
|
|
41
|
+
*/
|
|
42
|
+
toBuffer(buff = Buffer.alloc(this.getSize())) {
|
|
43
|
+
buff.writeUInt32LE(this.size, 0);
|
|
44
|
+
buff.writeUInt16LE(this.mode, 4);
|
|
45
|
+
buff.writeDoubleLE(this.atime, 6);
|
|
46
|
+
buff.writeDoubleLE(this.mtime, 14);
|
|
47
|
+
buff.writeDoubleLE(this.ctime, 22);
|
|
48
|
+
buff.writeUInt32LE(this.uid, 30);
|
|
49
|
+
buff.writeUInt32LE(this.gid, 34);
|
|
50
|
+
buff.write(this.id, 38, this.id.length, 'ascii');
|
|
51
|
+
return buff;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Updates the Inode using information from the stats object. Used by file
|
|
55
|
+
* systems at sync time, e.g.:
|
|
56
|
+
* - Program opens file and gets a File object.
|
|
57
|
+
* - Program mutates file. File object is responsible for maintaining
|
|
58
|
+
* metadata changes locally -- typically in a Stats object.
|
|
59
|
+
* - Program closes file. File object's metadata changes are synced with the
|
|
60
|
+
* file system.
|
|
61
|
+
* @return True if any changes have occurred.
|
|
62
|
+
*/
|
|
63
|
+
update(stats) {
|
|
64
|
+
let hasChanged = false;
|
|
65
|
+
if (this.size !== stats.size) {
|
|
66
|
+
this.size = stats.size;
|
|
67
|
+
hasChanged = true;
|
|
68
|
+
}
|
|
69
|
+
if (this.mode !== stats.mode) {
|
|
70
|
+
this.mode = stats.mode;
|
|
71
|
+
hasChanged = true;
|
|
72
|
+
}
|
|
73
|
+
const atimeMs = stats.atime.getTime();
|
|
74
|
+
if (this.atime !== atimeMs) {
|
|
75
|
+
this.atime = atimeMs;
|
|
76
|
+
hasChanged = true;
|
|
77
|
+
}
|
|
78
|
+
const mtimeMs = stats.mtime.getTime();
|
|
79
|
+
if (this.mtime !== mtimeMs) {
|
|
80
|
+
this.mtime = mtimeMs;
|
|
81
|
+
hasChanged = true;
|
|
82
|
+
}
|
|
83
|
+
const ctimeMs = stats.ctime.getTime();
|
|
84
|
+
if (this.ctime !== ctimeMs) {
|
|
85
|
+
this.ctime = ctimeMs;
|
|
86
|
+
hasChanged = true;
|
|
87
|
+
}
|
|
88
|
+
if (this.uid !== stats.uid) {
|
|
89
|
+
this.uid = stats.uid;
|
|
90
|
+
hasChanged = true;
|
|
91
|
+
}
|
|
92
|
+
if (this.uid !== stats.uid) {
|
|
93
|
+
this.uid = stats.uid;
|
|
94
|
+
hasChanged = true;
|
|
95
|
+
}
|
|
96
|
+
return hasChanged;
|
|
97
|
+
}
|
|
98
|
+
// XXX: Copied from Stats. Should reconcile these two into something more
|
|
99
|
+
// compact.
|
|
100
|
+
/**
|
|
101
|
+
* @return [Boolean] True if this item is a file.
|
|
102
|
+
*/
|
|
103
|
+
isFile() {
|
|
104
|
+
return (this.mode & 0xf000) === FileType.FILE;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* @return [Boolean] True if this item is a directory.
|
|
108
|
+
*/
|
|
109
|
+
isDirectory() {
|
|
110
|
+
return (this.mode & 0xf000) === FileType.DIRECTORY;
|
|
111
|
+
}
|
|
112
|
+
}
|
package/dist/mutex.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type MutexCallback = () => void;
|
|
2
|
+
/**
|
|
3
|
+
* Non-recursive mutex
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export default class Mutex {
|
|
7
|
+
private _locks;
|
|
8
|
+
lock(path: string): Promise<void>;
|
|
9
|
+
unlock(path: string): void;
|
|
10
|
+
tryLock(path: string): boolean;
|
|
11
|
+
isLocked(path: string): boolean;
|
|
12
|
+
}
|
package/dist/mutex.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-recursive mutex
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
export default class Mutex {
|
|
6
|
+
constructor() {
|
|
7
|
+
this._locks = new Map();
|
|
8
|
+
}
|
|
9
|
+
lock(path) {
|
|
10
|
+
return new Promise(resolve => {
|
|
11
|
+
if (this._locks.has(path)) {
|
|
12
|
+
this._locks.get(path).push(resolve);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
this._locks.set(path, []);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
unlock(path) {
|
|
20
|
+
if (!this._locks.has(path)) {
|
|
21
|
+
throw new Error('unlock of a non-locked mutex');
|
|
22
|
+
}
|
|
23
|
+
const next = this._locks.get(path).shift();
|
|
24
|
+
/*
|
|
25
|
+
don't unlock - we want to queue up next for the
|
|
26
|
+
end of the current task execution, but we don't
|
|
27
|
+
want it to be called inline with whatever the
|
|
28
|
+
current stack is. This way we still get the nice
|
|
29
|
+
behavior that an unlock immediately followed by a
|
|
30
|
+
lock won't cause starvation.
|
|
31
|
+
*/
|
|
32
|
+
if (next) {
|
|
33
|
+
setTimeout(next, 0);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this._locks.delete(path);
|
|
37
|
+
}
|
|
38
|
+
tryLock(path) {
|
|
39
|
+
if (this._locks.has(path)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
this._locks.set(path, []);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
isLocked(path) {
|
|
46
|
+
return this._locks.has(path);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import type { StatsBase } from 'fs';
|
|
4
|
+
import { Cred } from './cred';
|
|
5
|
+
/**
|
|
6
|
+
* Indicates the type of the given file. Applied to 'mode'.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum FileType {
|
|
9
|
+
FILE,
|
|
10
|
+
DIRECTORY,
|
|
11
|
+
SYMLINK
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Implementation of Node's `Stats`.
|
|
15
|
+
*
|
|
16
|
+
* Attribute descriptions are from `man 2 stat'
|
|
17
|
+
* @see http://nodejs.org/api/fs.html#fs_class_fs_stats
|
|
18
|
+
* @see http://man7.org/linux/man-pages/man2/stat.2.html
|
|
19
|
+
*/
|
|
20
|
+
export declare class Stats implements StatsBase<number> {
|
|
21
|
+
static fromBuffer(buffer: Buffer): Stats;
|
|
22
|
+
/**
|
|
23
|
+
* Clones the stats object.
|
|
24
|
+
*/
|
|
25
|
+
static clone(s: Stats): Stats;
|
|
26
|
+
blocks: number;
|
|
27
|
+
mode: number;
|
|
28
|
+
dev: number;
|
|
29
|
+
ino: number;
|
|
30
|
+
rdev: number;
|
|
31
|
+
nlink: number;
|
|
32
|
+
blksize: number;
|
|
33
|
+
uid: number;
|
|
34
|
+
gid: number;
|
|
35
|
+
fileData: Buffer | null;
|
|
36
|
+
atimeMs: number;
|
|
37
|
+
mtimeMs: number;
|
|
38
|
+
ctimeMs: number;
|
|
39
|
+
birthtimeMs: number;
|
|
40
|
+
size: number;
|
|
41
|
+
get atime(): Date;
|
|
42
|
+
get mtime(): Date;
|
|
43
|
+
get ctime(): Date;
|
|
44
|
+
get birthtime(): Date;
|
|
45
|
+
/**
|
|
46
|
+
* Provides information about a particular entry in the file system.
|
|
47
|
+
* @param itemType Type of the item (FILE, DIRECTORY, SYMLINK, or SOCKET)
|
|
48
|
+
* @param size Size of the item in bytes. For directories/symlinks,
|
|
49
|
+
* this is normally the size of the struct that represents the item.
|
|
50
|
+
* @param mode Unix-style file mode (e.g. 0o644)
|
|
51
|
+
* @param atimeMs time of last access, in milliseconds since epoch
|
|
52
|
+
* @param mtimeMs time of last modification, in milliseconds since epoch
|
|
53
|
+
* @param ctimeMs time of last time file status was changed, in milliseconds since epoch
|
|
54
|
+
* @param uid the id of the user that owns the file
|
|
55
|
+
* @param gid the id of the group that owns the file
|
|
56
|
+
* @param birthtimeMs time of file creation, in milliseconds since epoch
|
|
57
|
+
*/
|
|
58
|
+
constructor(itemType: FileType, size: number, mode?: number, atimeMs?: number, mtimeMs?: number, ctimeMs?: number, uid?: number, gid?: number, birthtimeMs?: number);
|
|
59
|
+
toBuffer(): Buffer;
|
|
60
|
+
/**
|
|
61
|
+
* @return [Boolean] True if this item is a file.
|
|
62
|
+
*/
|
|
63
|
+
isFile(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* @return [Boolean] True if this item is a directory.
|
|
66
|
+
*/
|
|
67
|
+
isDirectory(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* @return [Boolean] True if this item is a symbolic link (only valid through lstat)
|
|
70
|
+
*/
|
|
71
|
+
isSymbolicLink(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Checks if a given user/group has access to this item
|
|
74
|
+
* @param mode The request access as 4 bits (unused, read, write, execute)
|
|
75
|
+
* @param uid The requesting UID
|
|
76
|
+
* @param gid The requesting GID
|
|
77
|
+
* @returns [Boolean] True if the request has access, false if the request does not
|
|
78
|
+
*/
|
|
79
|
+
hasAccess(mode: number, cred: Cred): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Convert the current stats object into a cred object
|
|
82
|
+
*/
|
|
83
|
+
getCred(uid?: number, gid?: number): Cred;
|
|
84
|
+
/**
|
|
85
|
+
* Change the mode of the file. We use this helper function to prevent messing
|
|
86
|
+
* up the type of the file, which is encoded in mode.
|
|
87
|
+
*/
|
|
88
|
+
chmod(mode: number): void;
|
|
89
|
+
/**
|
|
90
|
+
* Change the owner user/group of the file.
|
|
91
|
+
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
92
|
+
*/
|
|
93
|
+
chown(uid: number, gid: number): void;
|
|
94
|
+
isSocket(): boolean;
|
|
95
|
+
isBlockDevice(): boolean;
|
|
96
|
+
isCharacterDevice(): boolean;
|
|
97
|
+
isFIFO(): boolean;
|
|
98
|
+
}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Cred } from './cred';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
import { S_IFDIR, S_IFLNK, S_IFMT, S_IFREG } from './emulation/constants';
|
|
4
|
+
/**
|
|
5
|
+
* Indicates the type of the given file. Applied to 'mode'.
|
|
6
|
+
*/
|
|
7
|
+
export var FileType;
|
|
8
|
+
(function (FileType) {
|
|
9
|
+
FileType[FileType["FILE"] = S_IFREG] = "FILE";
|
|
10
|
+
FileType[FileType["DIRECTORY"] = S_IFDIR] = "DIRECTORY";
|
|
11
|
+
FileType[FileType["SYMLINK"] = S_IFLNK] = "SYMLINK";
|
|
12
|
+
})(FileType || (FileType = {}));
|
|
13
|
+
/**
|
|
14
|
+
* Implementation of Node's `Stats`.
|
|
15
|
+
*
|
|
16
|
+
* Attribute descriptions are from `man 2 stat'
|
|
17
|
+
* @see http://nodejs.org/api/fs.html#fs_class_fs_stats
|
|
18
|
+
* @see http://man7.org/linux/man-pages/man2/stat.2.html
|
|
19
|
+
*/
|
|
20
|
+
export class Stats {
|
|
21
|
+
static fromBuffer(buffer) {
|
|
22
|
+
const size = buffer.readUInt32LE(0), mode = buffer.readUInt32LE(4), atime = buffer.readDoubleLE(8), mtime = buffer.readDoubleLE(16), ctime = buffer.readDoubleLE(24), uid = buffer.readUInt32LE(32), gid = buffer.readUInt32LE(36);
|
|
23
|
+
return new Stats(mode & S_IFMT, size, mode & ~S_IFMT, atime, mtime, ctime, uid, gid);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Clones the stats object.
|
|
27
|
+
*/
|
|
28
|
+
static clone(s) {
|
|
29
|
+
return new Stats(s.mode & S_IFMT, s.size, s.mode & ~S_IFMT, s.atimeMs, s.mtimeMs, s.ctimeMs, s.uid, s.gid, s.birthtimeMs);
|
|
30
|
+
}
|
|
31
|
+
get atime() {
|
|
32
|
+
return new Date(this.atimeMs);
|
|
33
|
+
}
|
|
34
|
+
get mtime() {
|
|
35
|
+
return new Date(this.mtimeMs);
|
|
36
|
+
}
|
|
37
|
+
get ctime() {
|
|
38
|
+
return new Date(this.ctimeMs);
|
|
39
|
+
}
|
|
40
|
+
get birthtime() {
|
|
41
|
+
return new Date(this.birthtimeMs);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Provides information about a particular entry in the file system.
|
|
45
|
+
* @param itemType Type of the item (FILE, DIRECTORY, SYMLINK, or SOCKET)
|
|
46
|
+
* @param size Size of the item in bytes. For directories/symlinks,
|
|
47
|
+
* this is normally the size of the struct that represents the item.
|
|
48
|
+
* @param mode Unix-style file mode (e.g. 0o644)
|
|
49
|
+
* @param atimeMs time of last access, in milliseconds since epoch
|
|
50
|
+
* @param mtimeMs time of last modification, in milliseconds since epoch
|
|
51
|
+
* @param ctimeMs time of last time file status was changed, in milliseconds since epoch
|
|
52
|
+
* @param uid the id of the user that owns the file
|
|
53
|
+
* @param gid the id of the group that owns the file
|
|
54
|
+
* @param birthtimeMs time of file creation, in milliseconds since epoch
|
|
55
|
+
*/
|
|
56
|
+
constructor(itemType, size, mode, atimeMs, mtimeMs, ctimeMs, uid, gid, birthtimeMs) {
|
|
57
|
+
// ID of device containing file
|
|
58
|
+
this.dev = 0;
|
|
59
|
+
// inode number
|
|
60
|
+
this.ino = 0;
|
|
61
|
+
// device ID (if special file)
|
|
62
|
+
this.rdev = 0;
|
|
63
|
+
// number of hard links
|
|
64
|
+
this.nlink = 1;
|
|
65
|
+
// blocksize for file system I/O
|
|
66
|
+
this.blksize = 4096;
|
|
67
|
+
// user ID of owner
|
|
68
|
+
this.uid = 0;
|
|
69
|
+
// group ID of owner
|
|
70
|
+
this.gid = 0;
|
|
71
|
+
// Some file systems stash data on stats objects.
|
|
72
|
+
this.fileData = null;
|
|
73
|
+
this.size = size;
|
|
74
|
+
let currentTime = 0;
|
|
75
|
+
if (typeof atimeMs !== 'number') {
|
|
76
|
+
currentTime = Date.now();
|
|
77
|
+
atimeMs = currentTime;
|
|
78
|
+
}
|
|
79
|
+
if (typeof mtimeMs !== 'number') {
|
|
80
|
+
if (!currentTime) {
|
|
81
|
+
currentTime = Date.now();
|
|
82
|
+
}
|
|
83
|
+
mtimeMs = currentTime;
|
|
84
|
+
}
|
|
85
|
+
if (typeof ctimeMs !== 'number') {
|
|
86
|
+
if (!currentTime) {
|
|
87
|
+
currentTime = Date.now();
|
|
88
|
+
}
|
|
89
|
+
ctimeMs = currentTime;
|
|
90
|
+
}
|
|
91
|
+
if (typeof birthtimeMs !== 'number') {
|
|
92
|
+
if (!currentTime) {
|
|
93
|
+
currentTime = Date.now();
|
|
94
|
+
}
|
|
95
|
+
birthtimeMs = currentTime;
|
|
96
|
+
}
|
|
97
|
+
if (typeof uid !== 'number') {
|
|
98
|
+
uid = 0;
|
|
99
|
+
}
|
|
100
|
+
if (typeof gid !== 'number') {
|
|
101
|
+
gid = 0;
|
|
102
|
+
}
|
|
103
|
+
this.atimeMs = atimeMs;
|
|
104
|
+
this.ctimeMs = ctimeMs;
|
|
105
|
+
this.mtimeMs = mtimeMs;
|
|
106
|
+
this.birthtimeMs = birthtimeMs;
|
|
107
|
+
if (!mode) {
|
|
108
|
+
switch (itemType) {
|
|
109
|
+
case FileType.FILE:
|
|
110
|
+
this.mode = 0o644;
|
|
111
|
+
break;
|
|
112
|
+
case FileType.DIRECTORY:
|
|
113
|
+
default:
|
|
114
|
+
this.mode = 0o777;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.mode = mode;
|
|
119
|
+
}
|
|
120
|
+
// number of 512B blocks allocated
|
|
121
|
+
this.blocks = Math.ceil(size / 512);
|
|
122
|
+
// Check if mode also includes top-most bits, which indicate the file's
|
|
123
|
+
// type.
|
|
124
|
+
if ((this.mode & S_IFMT) == 0) {
|
|
125
|
+
this.mode |= itemType;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
toBuffer() {
|
|
129
|
+
const buffer = Buffer.alloc(32);
|
|
130
|
+
buffer.writeUInt32LE(this.size, 0);
|
|
131
|
+
buffer.writeUInt32LE(this.mode, 4);
|
|
132
|
+
buffer.writeDoubleLE(this.atime.getTime(), 8);
|
|
133
|
+
buffer.writeDoubleLE(this.mtime.getTime(), 16);
|
|
134
|
+
buffer.writeDoubleLE(this.ctime.getTime(), 24);
|
|
135
|
+
buffer.writeUInt32LE(this.uid, 32);
|
|
136
|
+
buffer.writeUInt32LE(this.gid, 36);
|
|
137
|
+
return buffer;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* @return [Boolean] True if this item is a file.
|
|
141
|
+
*/
|
|
142
|
+
isFile() {
|
|
143
|
+
return (this.mode & S_IFMT) === S_IFREG;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @return [Boolean] True if this item is a directory.
|
|
147
|
+
*/
|
|
148
|
+
isDirectory() {
|
|
149
|
+
return (this.mode & S_IFMT) === S_IFDIR;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* @return [Boolean] True if this item is a symbolic link (only valid through lstat)
|
|
153
|
+
*/
|
|
154
|
+
isSymbolicLink() {
|
|
155
|
+
return (this.mode & S_IFMT) === S_IFLNK;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Checks if a given user/group has access to this item
|
|
159
|
+
* @param mode The request access as 4 bits (unused, read, write, execute)
|
|
160
|
+
* @param uid The requesting UID
|
|
161
|
+
* @param gid The requesting GID
|
|
162
|
+
* @returns [Boolean] True if the request has access, false if the request does not
|
|
163
|
+
*/
|
|
164
|
+
hasAccess(mode, cred) {
|
|
165
|
+
if (cred.euid === 0 || cred.egid === 0) {
|
|
166
|
+
//Running as root
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const perms = this.mode & ~S_IFMT;
|
|
170
|
+
let uMode = 0xf, gMode = 0xf, wMode = 0xf;
|
|
171
|
+
if (cred.euid == this.uid) {
|
|
172
|
+
const uPerms = (0xf00 & perms) >> 8;
|
|
173
|
+
uMode = (mode ^ uPerms) & mode;
|
|
174
|
+
}
|
|
175
|
+
if (cred.egid == this.gid) {
|
|
176
|
+
const gPerms = (0xf0 & perms) >> 4;
|
|
177
|
+
gMode = (mode ^ gPerms) & mode;
|
|
178
|
+
}
|
|
179
|
+
const wPerms = 0xf & perms;
|
|
180
|
+
wMode = (mode ^ wPerms) & mode;
|
|
181
|
+
/*
|
|
182
|
+
Result = 0b0xxx (read, write, execute)
|
|
183
|
+
If any bits are set that means the request does not have that permission.
|
|
184
|
+
*/
|
|
185
|
+
const result = uMode & gMode & wMode;
|
|
186
|
+
return !result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Convert the current stats object into a cred object
|
|
190
|
+
*/
|
|
191
|
+
getCred(uid = this.uid, gid = this.gid) {
|
|
192
|
+
return new Cred(uid, gid, this.uid, this.gid, uid, gid);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Change the mode of the file. We use this helper function to prevent messing
|
|
196
|
+
* up the type of the file, which is encoded in mode.
|
|
197
|
+
*/
|
|
198
|
+
chmod(mode) {
|
|
199
|
+
this.mode = (this.mode & S_IFMT) | mode;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Change the owner user/group of the file.
|
|
203
|
+
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
204
|
+
*/
|
|
205
|
+
chown(uid, gid) {
|
|
206
|
+
if (!isNaN(+uid) && 0 <= +uid && +uid < Math.pow(2, 32)) {
|
|
207
|
+
this.uid = uid;
|
|
208
|
+
}
|
|
209
|
+
if (!isNaN(+gid) && 0 <= +gid && +gid < Math.pow(2, 32)) {
|
|
210
|
+
this.gid = gid;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// We don't support the following types of files.
|
|
214
|
+
isSocket() {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
isBlockDevice() {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
isCharacterDevice() {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
isFIFO() {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|