@k13engineering/linux-fuse 0.0.6 → 0.1.0

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/dist/lib/index.js CHANGED
@@ -1,292 +1,20 @@
1
- import { createFuseLowLevel } from "./fuse-lowlevel.js";
2
- import { createFuseServer, /*type TRequestHandler */} from "./server.js";
3
- import {
4
- FSCONFIG_CMD_CREATE,
5
- FSCONFIG_SET_FLAG,
6
- FSCONFIG_SET_STRING,
7
- FSMOUNT_CLOEXEC,
8
- FSOPEN_CLOEXEC,
9
- } from "./constants.js";
10
- import nodeFs from "node:fs";
11
- import { fsconfig, fsmount, fsopen } from "./syscalls.js";
12
- import { openFuseFd, defaultFusePath } from "./linux.js";
13
1
  import { createConvenienceRequestHandler } from "./convenience.js";
14
- import {
15
- mountAttributesToBitmask,
16
- moveMount,
17
- unmount,
18
- /*type TMountAttributes,
19
- *//*type TRequiredFuseMountOptions
20
- */} from "./mount.js";
21
- import { createPoller } from "@k13engineering/uv-poll";
2
+ import { linuxKernel } from "./kernel.js";
3
+ import { createLinuxFuse } from "./linux-fuse.js";
4
+ import { defaultFusePath } from "./linux.js";
5
+ /*import type { TMountAttributes, TRequiredFuseMountOptions } from "./mount.ts";*/
22
6
  /*import type { TFuseResponse } from "./protocol.ts";*/
23
7
  /*import type { TFuseServerInterface } from "./convenience.ts";*/
24
8
  /*import type { TDirent } from "./dirent.ts";*/
25
9
  import { direntsToBuffer } from "./dirent.js";
26
10
 
27
- const UV_EBADF = -9;
28
-
29
- const MAX_ALLOWED_WRITE = 32n * 1024n * 1024n;
30
-
31
- const createFuseFileSystem = ({
32
- fuseFd,
33
- requestHandler,
34
- sniff,
35
- }/*: {
36
- fuseFd: number;
37
- requestHandler: TRequestHandler;
38
-
39
- sniff?: {
40
- rawMessageFromKernel: (args: { data: Uint8Array }) => void;
41
- rawMessageToKernel: (args: { data: Uint8Array }) => void;
42
- }
43
- }*/) => {
44
-
45
- let lowLevel = createFuseLowLevel({
46
- fuseFd,
47
- maxMessageSize: 1 * 1024 * 1024 + 4096,
48
- });
49
-
50
- const server = createFuseServer({
51
- requestHandler: {
52
- // eslint-disable-next-line complexity
53
- handleRequest: async ({ request }) => {
54
- const response = await requestHandler.handleRequest({ request });
55
-
56
- if (response !== undefined && response.forOpcode === "INIT" && response.errorCode === undefined) {
57
-
58
- if (response.result.maxWrite >= MAX_ALLOWED_WRITE) {
59
- throw Error(`maxWrite of ${response.result.maxWrite} is too large, refusing to operate`);
60
- }
61
-
62
- lowLevel = createFuseLowLevel({
63
- fuseFd,
64
- maxMessageSize: Number(response.result.maxWrite) + 4096,
65
- });
66
- }
67
-
68
- return response;
69
- }
70
- },
71
- sendMessageToKernel: ({ rawMessageToKernel }) => {
72
- sniff?.rawMessageToKernel({ data: rawMessageToKernel });
73
- lowLevel.writeOutgoingMessage({
74
- rawMessage: rawMessageToKernel
75
- });
76
- }
77
- });
78
-
79
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
80
- let ended = false;
81
- let closed = false;
82
-
83
- const poller = createPoller({
84
- fd: fuseFd
85
- });
86
-
87
- const handlePollError = ({ errorCode }/*: { errorCode: number }*/) => {
88
- if (errorCode === UV_EBADF) {
89
- // EBADF is raised when the kernel unmounts the filesystem
90
- ended = true;
91
- return;
92
- }
93
-
94
- throw Error(`poll error with code ${errorCode}`);
95
- };
96
-
97
- // eslint-disable-next-line complexity
98
- const next = () => {
99
-
100
- if (closed) {
101
- throw Error("BUG: should not be called after close");
102
- }
103
-
104
- const { error: readError, incomingMessage, end } = lowLevel.tryReadIncomingMessage();
105
- if (readError !== undefined) {
106
- throw readError;
107
- }
108
-
109
- if (end) {
110
- ended = true;
111
- return;
112
- }
113
-
114
- if (incomingMessage !== undefined) {
115
- sniff?.rawMessageFromKernel({ data: incomingMessage });
116
- server.processRequest({
117
- rawIncomingRequest: incomingMessage
118
- });
119
- }
120
-
121
- poller.armOnce({
122
- error: ({ errorCode }) => {
123
- handlePollError({ errorCode });
124
- },
125
-
126
- readable: () => {
127
- next();
128
- }
129
- });
130
- };
131
-
132
- poller.armOnce({
133
- error: ({ errorCode }) => {
134
- handlePollError({ errorCode });
135
- },
136
-
137
- readable: () => {
138
- next();
139
- }
140
- });
141
-
142
- /*type TFsMountResult = {
143
- error: Error;
144
- mountFd: undefined;
145
- } | {
146
- error: undefined;
147
- mountFd: number;
148
- };*/
149
-
150
- // eslint-disable-next-line max-statements
151
- const mountDetached = ({
152
- otherOptions,
153
- requiredOptions,
154
- mountAttributes,
155
- }/*: {
156
- otherOptions: {
157
- default_permissions?: boolean;
158
- allow_other?: boolean;
159
- allow_root?: boolean;
160
- } | Record<string, string | boolean>;
161
- requiredOptions: TRequiredFuseMountOptions;
162
- mountAttributes: TMountAttributes;
163
- }*/)/*: TFsMountResult*/ => {
164
-
165
- const { errno: fsopenErrno, fsfd } = fsopen({
166
- fileSystemName: "fuse",
167
- flags: FSOPEN_CLOEXEC
168
- });
169
-
170
- if (fsopenErrno !== undefined) {
171
- return {
172
- error: Error(`fsopen syscall failed with errno ${fsopenErrno}`),
173
- mountFd: undefined,
174
- };
175
- }
176
-
177
- const configureString = ({ key, value }/*: { key: string; value: string }*/) => {
178
- const { errno } = fsconfig({
179
- fd: fsfd,
180
- command: FSCONFIG_SET_STRING,
181
- key,
182
- value,
183
- auxiliaryValue: 0,
184
- });
185
-
186
- if (errno !== undefined) {
187
- throw Error(`fsconfig syscall failed with errno ${errno} when setting ${key}=${value}`);
188
- }
189
- };
190
-
191
- const configureFlag = ({ key, value }/*: { key: string; value: boolean }*/) => {
192
- if (!value) {
193
- return;
194
- }
195
-
196
- const { errno } = fsconfig({
197
- fd: fsfd,
198
- command: FSCONFIG_SET_FLAG,
199
- key,
200
- value: undefined,
201
- auxiliaryValue: 0,
202
- });
203
-
204
- if (errno !== undefined) {
205
- throw Error(`fsconfig syscall failed with errno ${errno} when setting flag ${key}`);
206
- }
207
- };
208
-
209
- const forbiddenOptions = ["fd", "rootmode", "user_id", "group_id"];
210
-
211
- Object.keys(otherOptions).forEach((key) => {
212
- const value = otherOptions[key /*as keyof typeof otherOptions*/];
213
-
214
- if (forbiddenOptions.includes(key)) {
215
- throw Error(`option ${key} is not allowed as it is required for correct mounting`);
216
- }
217
-
218
- if (value === undefined) {
219
- return;
220
- }
221
-
222
- if (typeof value === "boolean") {
223
- configureFlag({ key, value });
224
- } else {
225
- configureString({ key, value });
226
- }
227
- });
228
-
229
- configureString({ key: "fd", value: `${fuseFd}` });
230
- configureString({ key: "rootmode", value: `${requiredOptions.rootmode.toString(8)}` });
231
- configureString({ key: "user_id", value: `${requiredOptions.user_id}` });
232
- configureString({ key: "group_id", value: `${requiredOptions.group_id}` });
233
-
234
- const { errno: createErrno } = fsconfig({
235
- fd: fsfd,
236
- command: FSCONFIG_CMD_CREATE,
237
- key: undefined,
238
- value: undefined,
239
- auxiliaryValue: 0,
240
- });
241
- if (createErrno !== undefined) {
242
-
243
- nodeFs.closeSync(fsfd);
244
-
245
- return {
246
- error: Error(`fsconfig CMD_CREATE syscall failed with errno ${createErrno}`),
247
- mountFd: undefined,
248
- };
249
- }
250
-
251
- const { errno: mountErrno, mountFd } = fsmount({
252
- fd: fsfd,
253
- flags: FSMOUNT_CLOEXEC,
254
- mountAttributesBitmask: mountAttributesToBitmask({
255
- mountAttributes
256
- })
257
- });
258
-
259
- nodeFs.closeSync(fsfd);
260
-
261
- if (mountErrno !== undefined) {
262
- return {
263
- error: Error(`fsmount syscall failed with errno ${mountErrno}`),
264
- mountFd: undefined,
265
- };
266
- }
267
-
268
- return {
269
- error: undefined,
270
- mountFd,
271
- };
272
- };
273
-
274
- const close = () => {
275
- if (closed) {
276
- throw Error("already closed");
277
- }
278
-
279
- closed = true;
280
-
281
- poller.close();
282
- nodeFs.closeSync(fuseFd);
283
- };
284
-
285
- return {
286
- mountDetached,
287
- close,
288
- };
289
- };
11
+ const {
12
+ createFuseFileSystem,
13
+ openFuseFd,
14
+ moveMount,
15
+ unmount,
16
+ fchdir,
17
+ } = createLinuxFuse({ kernel: linuxKernel });
290
18
 
291
19
  const createDefaultInitResult = ()/*: Extract<TFuseResponse, { forOpcode: "INIT", errorCode: undefined }>["result"]*/ => {
292
20
  return {
@@ -314,7 +42,8 @@ export {
314
42
  moveMount,
315
43
  unmount,
316
44
  createDefaultInitResult,
317
- direntsToBuffer
45
+ direntsToBuffer,
46
+ fchdir,
318
47
  };
319
48
 
320
49
  /*export type {
@@ -0,0 +1,9 @@
1
+ import { createPoller } from "@k13engineering/uv-poll";
2
+ import { syscall } from "syscall-napi";
3
+ type TKernel = {
4
+ syscall: typeof syscall;
5
+ createPoller: typeof createPoller;
6
+ };
7
+ declare const linuxKernel: TKernel;
8
+ export { linuxKernel, };
9
+ export type { TKernel, };
@@ -0,0 +1,21 @@
1
+ import { createPoller } from "@k13engineering/uv-poll";
2
+ import { syscall } from "syscall-napi";
3
+
4
+ // everything that needs the real kernel goes through this interface, so it can be mocked in tests
5
+ /*type TKernel = {
6
+ syscall: typeof syscall;
7
+ createPoller: typeof createPoller;
8
+ };*/
9
+
10
+ const linuxKernel/*: TKernel*/ = {
11
+ syscall,
12
+ createPoller,
13
+ };
14
+
15
+ export {
16
+ linuxKernel,
17
+ };
18
+
19
+ /*export type {
20
+ TKernel,
21
+ };*/
@@ -0,0 +1,21 @@
1
+ import { type TFuseFileSystemOptions } from "./filesystem.js";
2
+ import type { TKernel } from "./kernel.js";
3
+ declare const createLinuxFuse: ({ kernel }: {
4
+ kernel: TKernel;
5
+ }) => {
6
+ createFuseFileSystem: (options: TFuseFileSystemOptions) => any;
7
+ openFuseFd: ({ fusePath }: {
8
+ fusePath: string;
9
+ }) => any;
10
+ moveMount: ({ mountFd, newMountPoint }: {
11
+ mountFd: number;
12
+ newMountPoint: string;
13
+ }) => any;
14
+ unmount: ({ mountPoint, force, detach }: {
15
+ mountPoint: string;
16
+ force: boolean;
17
+ detach: boolean;
18
+ }) => any;
19
+ fchdir: any;
20
+ };
21
+ export { createLinuxFuse, };
@@ -0,0 +1,38 @@
1
+ import { createFuseFileSystem, /*type TFuseFileSystemOptions */} from "./filesystem.js";
2
+ /*import type { TKernel } from "./kernel.ts";*/
3
+ import { openFuseFd } from "./linux.js";
4
+ import { moveMount, unmount } from "./mount.js";
5
+ import { createSyscalls } from "./syscalls.js";
6
+
7
+ // binds the public API to a kernel, the default instance uses the real one
8
+ const createLinuxFuse = ({ kernel }/*: { kernel: TKernel }*/) => {
9
+ const syscalls = createSyscalls({ kernel });
10
+
11
+ return {
12
+ createFuseFileSystem: (options/*: TFuseFileSystemOptions*/) => {
13
+ return createFuseFileSystem({
14
+ ...options,
15
+ syscalls,
16
+ createPoller: kernel.createPoller,
17
+ });
18
+ },
19
+
20
+ openFuseFd: ({ fusePath }/*: { fusePath: string }*/) => {
21
+ return openFuseFd({ syscalls, fusePath });
22
+ },
23
+
24
+ moveMount: ({ mountFd, newMountPoint }/*: { mountFd: number; newMountPoint: string }*/) => {
25
+ return moveMount({ syscalls, mountFd, newMountPoint });
26
+ },
27
+
28
+ unmount: ({ mountPoint, force, detach }/*: { mountPoint: string; force: boolean; detach: boolean }*/) => {
29
+ return unmount({ syscalls, mountPoint, force, detach });
30
+ },
31
+
32
+ fchdir: syscalls.fchdir,
33
+ };
34
+ };
35
+
36
+ export {
37
+ createLinuxFuse,
38
+ };
@@ -1,3 +1,4 @@
1
+ import type { TSyscalls } from "./syscalls.js";
1
2
  type TOpenResult = {
2
3
  error: Error;
3
4
  fuseFd: undefined;
@@ -6,7 +7,8 @@ type TOpenResult = {
6
7
  fuseFd: number;
7
8
  };
8
9
  declare const defaultFusePath = "/dev/fuse";
9
- declare const openFuseFd: ({ fusePath }: {
10
+ declare const openFuseFd: ({ syscalls, fusePath }: {
11
+ syscalls: Pick<TSyscalls, "openat">;
10
12
  fusePath: string;
11
13
  }) => TOpenResult;
12
14
  export { defaultFusePath, openFuseFd };
package/dist/lib/linux.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import nodeFs from "node:fs";
2
+ import { AT_FCWD, O_CLOEXEC } from "./constants.js";
3
+ /*import type { TSyscalls } from "./syscalls.ts";*/
2
4
 
3
5
  /*type TOpenResult = {
4
6
  error: Error;
@@ -10,20 +12,25 @@ import nodeFs from "node:fs";
10
12
 
11
13
  const defaultFusePath = "/dev/fuse";
12
14
 
13
- const openFuseFd = ({ fusePath }/*: { fusePath: string }*/)/*: TOpenResult*/ => {
14
- try {
15
- const fuseFd = nodeFs.openSync(fusePath, nodeFs.constants.O_RDWR | nodeFs.constants.O_NONBLOCK);
15
+ const openFuseFd = ({ syscalls, fusePath }/*: { syscalls: Pick<TSyscalls, "openat">; fusePath: string }*/)/*: TOpenResult*/ => {
16
+ const { errno, fd } = syscalls.openat({
17
+ dirFd: Number(AT_FCWD),
18
+ path: fusePath,
19
+ flags: nodeFs.constants.O_RDWR | nodeFs.constants.O_NONBLOCK | O_CLOEXEC,
20
+ mode: 0,
21
+ });
16
22
 
23
+ if (errno !== undefined) {
17
24
  return {
18
- error: undefined,
19
- fuseFd,
20
- };
21
- } catch (err) {
22
- return {
23
- error: Error(`failed to open "${defaultFusePath}": ${err}`),
25
+ error: Error(`failed to open "${fusePath}": openat syscall failed with errno ${errno}`),
24
26
  fuseFd: undefined,
25
27
  };
26
28
  }
29
+
30
+ return {
31
+ error: undefined,
32
+ fuseFd: fd,
33
+ };
27
34
  };
28
35
 
29
36
  export {
@@ -132,6 +132,12 @@ const createMessageToKernelParser = () => {
132
132
 
133
133
  const parseMessageToKernel = ({ data }/*: { data: Uint8Array }*/)/*: TParseMessageToKernelResult*/ => {
134
134
  const outHeader = data.subarray(0, fuseOutHeaderParser.size);
135
+ if (outHeader.length < fuseOutHeaderParser.size) {
136
+ return {
137
+ error: Error(`invalid message: expected at least ${fuseOutHeaderParser.size} bytes for header, got ${outHeader.length} bytes`),
138
+ parsedMessageToKernel: undefined,
139
+ };
140
+ }
135
141
 
136
142
  const parsedOutHeader = fuseOutHeaderParser.parse({
137
143
  data: outHeader
@@ -1,3 +1,4 @@
1
+ import type { TSyscalls } from "./syscalls.js";
1
2
  type TRequiredFuseMountOptions = {
2
3
  rootmode: number;
3
4
  user_id: number;
@@ -18,13 +19,15 @@ declare const mountAttributesToBitmask: ({ mountAttributes }: {
18
19
  type TUnmountResult = {
19
20
  error: Error | undefined;
20
21
  };
21
- declare const moveMount: ({ mountFd, newMountPoint, }: {
22
+ declare const moveMount: ({ syscalls, mountFd, newMountPoint, }: {
23
+ syscalls: Pick<TSyscalls, "move_mount">;
22
24
  mountFd: number;
23
25
  newMountPoint: string;
24
26
  }) => {
25
27
  error: any;
26
28
  };
27
- declare const unmount: ({ mountPoint, force, detach }: {
29
+ declare const unmount: ({ syscalls, mountPoint, force, detach, }: {
30
+ syscalls: Pick<TSyscalls, "umount">;
28
31
  mountPoint: string;
29
32
  force: boolean;
30
33
  detach: boolean;
package/dist/lib/mount.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  MOUNT_ATTR_RDONLY,
9
9
  MOVE_MOUNT_F_EMPTY_PATH
10
10
  } from "./constants.js";
11
- import { move_mount, umount } from "./syscalls.js";
11
+ /*import type { TSyscalls } from "./syscalls.ts";*/
12
12
 
13
13
  /*type TRequiredFuseMountOptions = {
14
14
  rootmode: number;
@@ -84,13 +84,15 @@ const MNT_DETACH = 2n;
84
84
 
85
85
  // for now, only export this simple variant for use with fuse
86
86
  const moveMount = ({
87
+ syscalls,
87
88
  mountFd,
88
89
  newMountPoint,
89
90
  }/*: {
91
+ syscalls: Pick<TSyscalls, "move_mount">;
90
92
  mountFd: number;
91
93
  newMountPoint: string;
92
94
  }*/) => {
93
- const { errno: moveMountErrno } = move_mount({
95
+ const { errno: moveMountErrno } = syscalls.move_mount({
94
96
  fromFd: mountFd,
95
97
  fromPath: "",
96
98
  toFd: Number(AT_FCWD),
@@ -109,7 +111,17 @@ const moveMount = ({
109
111
  };
110
112
  };
111
113
 
112
- const unmount = ({ mountPoint, force, detach }/*: { mountPoint: string, force: boolean, detach: boolean }*/)/*: TUnmountResult*/ => {
114
+ const unmount = ({
115
+ syscalls,
116
+ mountPoint,
117
+ force,
118
+ detach,
119
+ }/*: {
120
+ syscalls: Pick<TSyscalls, "umount">;
121
+ mountPoint: string;
122
+ force: boolean;
123
+ detach: boolean;
124
+ }*/)/*: TUnmountResult*/ => {
113
125
 
114
126
  let flags = 0n;
115
127
 
@@ -121,7 +133,7 @@ const unmount = ({ mountPoint, force, detach }/*: { mountPoint: string, force: b
121
133
  flags |= MNT_DETACH;
122
134
  }
123
135
 
124
- const { errno } = umount({
136
+ const { errno } = syscalls.umount({
125
137
  target: mountPoint,
126
138
  flags,
127
139
  });
@@ -892,6 +892,12 @@ type TGenericInterpretResult<T> = {
892
892
  interpreted: T;
893
893
  };
894
894
  declare const createMessageFromKernelInterpreter: () => {
895
+ applyInitResult: ({ result }: {
896
+ result: Extract<TFuseResponse, {
897
+ forOpcode: "INIT";
898
+ errorCode: undefined;
899
+ }>["result"];
900
+ }) => void;
895
901
  interpretMessageFromKernel: ({ parsedMessageFromKernel }: {
896
902
  parsedMessageFromKernel: TParsedMessageFromKernel;
897
903
  }) => TGenericInterpretResult<TFuseRequest>;