Nfsv4FsClient.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Nfsv4FsClient = void 0;
  4. const tslib_1 = require("tslib");
  5. const builder_1 = require("../builder");
  6. const structs = tslib_1.__importStar(require("../structs"));
  7. const Writer_1 = require("@jsonjoy.com/buffers/lib/Writer");
  8. const Reader_1 = require("@jsonjoy.com/buffers/lib/Reader");
  9. const XdrEncoder_1 = require("../../../xdr/XdrEncoder");
  10. const XdrDecoder_1 = require("../../../xdr/XdrDecoder");
  11. const NfsFsStats_1 = require("./NfsFsStats");
  12. const NfsFsDir_1 = require("./NfsFsDir");
  13. const NfsFsDirent_1 = require("./NfsFsDirent");
  14. const NfsFsFileHandle_1 = require("./NfsFsFileHandle");
  15. class Nfsv4FsClient {
  16. constructor(fs) {
  17. this.fs = fs;
  18. this.openOwnerSeqids = new Map();
  19. this.defaultOpenOwnerId = new Uint8Array([1, 2, 3, 4]);
  20. this.closeStateid = async (openOwner, stateid) => {
  21. const key = this.makeOpenOwnerKey(openOwner);
  22. const previousSeqid = this.openOwnerSeqids.get(key);
  23. const seqid = this.nextOpenOwnerSeqid(openOwner);
  24. const response = await this.fs.compound([builder_1.nfs.CLOSE(seqid, stateid)]);
  25. if (response.status !== 0) {
  26. if (previousSeqid !== undefined) {
  27. this.openOwnerSeqids.set(key, previousSeqid);
  28. }
  29. else {
  30. this.openOwnerSeqids.delete(key);
  31. }
  32. throw new Error(`Failed to close file: ${response.status}`);
  33. }
  34. };
  35. this.readFile = async (id, options) => {
  36. const encoding = typeof options === 'string' ? options : options?.encoding;
  37. const path = typeof id === 'string' ? id : id.toString();
  38. const parts = this.parsePath(path);
  39. const operations = this.navigateToParent(parts);
  40. const filename = parts[parts.length - 1];
  41. const openOwner = this.createDefaultOpenOwner();
  42. const claim = builder_1.nfs.OpenClaimNull(filename);
  43. const openSeqid = this.nextOpenOwnerSeqid(openOwner);
  44. operations.push(builder_1.nfs.OPEN(openSeqid, 1, 0, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
  45. const openResponse = await this.fs.compound(operations);
  46. if (openResponse.status !== 0) {
  47. throw new Error(`Failed to open file: ${openResponse.status}`);
  48. }
  49. const openRes = openResponse.resarray[openResponse.resarray.length - 1];
  50. if (openRes.status !== 0 || !openRes.resok) {
  51. throw new Error(`Failed to open file: ${openRes.status}`);
  52. }
  53. const stateid = openRes.resok.stateid;
  54. const chunks = [];
  55. let offset = BigInt(0);
  56. const chunkSize = 65536;
  57. try {
  58. while (true) {
  59. const readResponse = await this.fs.compound([builder_1.nfs.READ(offset, chunkSize, stateid)]);
  60. if (readResponse.status !== 0) {
  61. throw new Error(`Failed to read file: ${readResponse.status}`);
  62. }
  63. const readRes = readResponse.resarray[0];
  64. if (readRes.status !== 0 || !readRes.resok) {
  65. throw new Error(`Failed to read file: ${readRes.status}`);
  66. }
  67. if (readRes.resok.data.length > 0) {
  68. chunks.push(readRes.resok.data);
  69. offset += BigInt(readRes.resok.data.length);
  70. }
  71. if (readRes.resok.eof)
  72. break;
  73. }
  74. }
  75. finally {
  76. await this.closeStateid(openOwner, stateid);
  77. }
  78. const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
  79. const result = new Uint8Array(totalLength);
  80. let position = 0;
  81. for (const chunk of chunks) {
  82. result.set(chunk, position);
  83. position += chunk.length;
  84. }
  85. return this.decodeData(result, encoding);
  86. };
  87. this.writeFile = async (id, data, options) => {
  88. const path = typeof id === 'string' ? id : id.toString();
  89. const parts = this.parsePath(path);
  90. const operations = this.navigateToParent(parts);
  91. const filename = parts[parts.length - 1];
  92. const openOwner = this.createDefaultOpenOwner();
  93. const claim = builder_1.nfs.OpenClaimNull(filename);
  94. const openSeqid = this.nextOpenOwnerSeqid(openOwner);
  95. operations.push(builder_1.nfs.OPEN(openSeqid, 2, 0, openOwner, builder_1.nfs.OpenHowCreateUnchecked(), claim));
  96. const writer = new Writer_1.Writer(16);
  97. const xdr = new XdrEncoder_1.XdrEncoder(writer);
  98. xdr.writeUnsignedHyper(BigInt(0));
  99. const attrVals = writer.flush();
  100. const truncateAttrs = builder_1.nfs.Fattr([4], attrVals);
  101. const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
  102. operations.push(builder_1.nfs.SETATTR(stateid, truncateAttrs));
  103. const openResponse = await this.fs.compound(operations);
  104. if (openResponse.status !== 0) {
  105. throw new Error(`Failed to open file: ${openResponse.status}`);
  106. }
  107. const openRes = openResponse.resarray[openResponse.resarray.length - 2];
  108. if (openRes.status !== 0 || !openRes.resok) {
  109. throw new Error(`Failed to open file: ${openRes.status}`);
  110. }
  111. const openStateid = openRes.resok.stateid;
  112. const buffer = this.encodeData(data);
  113. const chunkSize = 65536;
  114. try {
  115. let offset = BigInt(0);
  116. for (let i = 0; i < buffer.length; i += chunkSize) {
  117. const chunk = buffer.slice(i, Math.min(i + chunkSize, buffer.length));
  118. const writeResponse = await this.fs.compound([
  119. builder_1.nfs.WRITE(openStateid, offset, 2, chunk),
  120. ]);
  121. if (writeResponse.status !== 0) {
  122. throw new Error(`Failed to write file: ${writeResponse.status}`);
  123. }
  124. const writeRes = writeResponse.resarray[0];
  125. if (writeRes.status !== 0 || !writeRes.resok) {
  126. throw new Error(`Failed to write file: ${writeRes.status}`);
  127. }
  128. offset += BigInt(writeRes.resok.count);
  129. }
  130. }
  131. finally {
  132. await this.closeStateid(openOwner, openStateid);
  133. }
  134. };
  135. this.stat = async (path, options) => {
  136. const pathStr = typeof path === 'string' ? path : path.toString();
  137. const parts = this.parsePath(pathStr);
  138. const operations = this.navigateToPath(parts);
  139. const attrNums = [
  140. 1,
  141. 4,
  142. 20,
  143. 33,
  144. 35,
  145. 45,
  146. 47,
  147. 53,
  148. 52,
  149. ];
  150. const attrMask = this.attrNumsToBitmap(attrNums);
  151. operations.push(builder_1.nfs.GETATTR(attrMask));
  152. const response = await this.fs.compound(operations);
  153. if (response.status !== 0) {
  154. throw new Error(`Failed to stat file: ${response.status}`);
  155. }
  156. const getattrRes = response.resarray[response.resarray.length - 1];
  157. if (getattrRes.status !== 0 || !getattrRes.resok) {
  158. throw new Error(`Failed to get attributes: ${getattrRes.status}`);
  159. }
  160. const fattr = getattrRes.resok.objAttributes;
  161. const reader = new Reader_1.Reader();
  162. reader.reset(fattr.attrVals);
  163. const xdr = new XdrDecoder_1.XdrDecoder(reader);
  164. let fileType = 1;
  165. let size = 0;
  166. let fileid = 0;
  167. let mode = 0;
  168. let nlink = 1;
  169. let spaceUsed = 0;
  170. let atime = new Date(0);
  171. let mtime = new Date(0);
  172. let ctime = new Date(0);
  173. const returnedMask = fattr.attrmask.mask;
  174. for (let i = 0; i < returnedMask.length; i++) {
  175. const word = returnedMask[i];
  176. if (!word)
  177. continue;
  178. for (let bit = 0; bit < 32; bit++) {
  179. if (!(word & (1 << bit)))
  180. continue;
  181. const attrNum = i * 32 + bit;
  182. switch (attrNum) {
  183. case 1:
  184. fileType = xdr.readUnsignedInt();
  185. break;
  186. case 4:
  187. size = Number(xdr.readUnsignedHyper());
  188. break;
  189. case 20:
  190. fileid = Number(xdr.readUnsignedHyper());
  191. break;
  192. case 33:
  193. mode = xdr.readUnsignedInt();
  194. break;
  195. case 35:
  196. nlink = xdr.readUnsignedInt();
  197. break;
  198. case 45:
  199. spaceUsed = Number(xdr.readUnsignedHyper());
  200. break;
  201. case 47: {
  202. const seconds = Number(xdr.readHyper());
  203. const nseconds = xdr.readUnsignedInt();
  204. atime = new Date(seconds * 1000 + nseconds / 1000000);
  205. break;
  206. }
  207. case 53: {
  208. const seconds = Number(xdr.readHyper());
  209. const nseconds = xdr.readUnsignedInt();
  210. mtime = new Date(seconds * 1000 + nseconds / 1000000);
  211. break;
  212. }
  213. case 52: {
  214. const seconds = Number(xdr.readHyper());
  215. const nseconds = xdr.readUnsignedInt();
  216. ctime = new Date(seconds * 1000 + nseconds / 1000000);
  217. break;
  218. }
  219. }
  220. }
  221. }
  222. const blocks = Math.ceil(spaceUsed / 512);
  223. return new NfsFsStats_1.NfsFsStats(0, 0, 0, 4096, fileid, size, blocks, atime, mtime, ctime, mtime, atime.getTime(), mtime.getTime(), ctime.getTime(), mtime.getTime(), 0, mode, nlink, fileType);
  224. };
  225. this.lstat = async (path, options) => {
  226. return this.stat(path, options);
  227. };
  228. this.mkdir = async (path, options) => {
  229. const pathStr = typeof path === 'string' ? path : path.toString();
  230. const parts = this.parsePath(pathStr);
  231. if (parts.length === 0) {
  232. throw new Error('Cannot create root directory');
  233. }
  234. const operations = this.navigateToParent(parts);
  235. const dirname = parts[parts.length - 1];
  236. const createType = builder_1.nfs.CreateTypeDir();
  237. const emptyAttrs = builder_1.nfs.Fattr([], new Uint8Array(0));
  238. operations.push(builder_1.nfs.CREATE(createType, dirname, emptyAttrs));
  239. const response = await this.fs.compound(operations);
  240. if (response.status !== 0) {
  241. throw new Error(`Failed to create directory: ${response.status}`);
  242. }
  243. const createRes = response.resarray[response.resarray.length - 1];
  244. if (createRes.status !== 0) {
  245. throw new Error(`Failed to create directory: ${createRes.status}`);
  246. }
  247. return undefined;
  248. };
  249. this.readdir = async (path, options) => {
  250. const pathStr = typeof path === 'string' ? path : path.toString();
  251. const withFileTypes = typeof options === 'object' && options?.withFileTypes;
  252. const encoding = typeof options === 'string' ? options : options?.encoding;
  253. const parts = this.parsePath(pathStr);
  254. const operations = this.navigateToPath(parts);
  255. const attrNums = withFileTypes ? [1] : [];
  256. const attrMask = this.attrNumsToBitmap(attrNums);
  257. operations.push(builder_1.nfs.READDIR(attrMask));
  258. const response = await this.fs.compound(operations);
  259. if (response.status !== 0) {
  260. throw new Error(`Failed to read directory: ${response.status}`);
  261. }
  262. const readdirRes = response.resarray[response.resarray.length - 1];
  263. if (readdirRes.status !== 0 || !readdirRes.resok) {
  264. throw new Error(`Failed to read directory: ${readdirRes.status}`);
  265. }
  266. const entries = [];
  267. const dirents = [];
  268. const entryList = readdirRes.resok.entries;
  269. for (let i = 0; i < entryList.length; i++) {
  270. const entry = entryList[i];
  271. const name = entry.name;
  272. if (withFileTypes) {
  273. const fattr = entry.attrs;
  274. const reader = new Reader_1.Reader();
  275. reader.reset(fattr.attrVals);
  276. const xdr = new XdrDecoder_1.XdrDecoder(reader);
  277. let fileType = 1;
  278. const returnedMask = fattr.attrmask.mask;
  279. for (let i = 0; i < returnedMask.length; i++) {
  280. const word = returnedMask[i];
  281. if (!word)
  282. continue;
  283. for (let bit = 0; bit < 32; bit++) {
  284. if (!(word & (1 << bit)))
  285. continue;
  286. const attrNum = i * 32 + bit;
  287. if (attrNum === 1) {
  288. fileType = xdr.readUnsignedInt();
  289. }
  290. }
  291. }
  292. dirents.push(new NfsFsDirent_1.NfsFsDirent(name, fileType));
  293. }
  294. else {
  295. entries.push(name);
  296. }
  297. }
  298. if (withFileTypes) {
  299. return dirents;
  300. }
  301. if (encoding && encoding !== 'utf8') {
  302. return entries.map((name) => Buffer.from(name, 'utf8'));
  303. }
  304. return entries;
  305. };
  306. this.appendFile = async (path, data, options) => {
  307. const pathStr = typeof path === 'string' ? path : path.toString();
  308. const parts = this.parsePath(pathStr);
  309. const operations = this.navigateToParent(parts);
  310. const filename = parts[parts.length - 1];
  311. const openOwner = this.createDefaultOpenOwner();
  312. const claim = builder_1.nfs.OpenClaimNull(filename);
  313. const openSeqid = this.nextOpenOwnerSeqid(openOwner);
  314. operations.push(builder_1.nfs.OPEN(openSeqid, 2, 0, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
  315. const attrNums = [4];
  316. const attrMask = this.attrNumsToBitmap(attrNums);
  317. operations.push(builder_1.nfs.GETATTR(attrMask));
  318. const openResponse = await this.fs.compound(operations);
  319. if (openResponse.status !== 0) {
  320. throw new Error(`Failed to open file: ${openResponse.status}`);
  321. }
  322. const openRes = openResponse.resarray[openResponse.resarray.length - 2];
  323. if (openRes.status !== 0 || !openRes.resok) {
  324. throw new Error(`Failed to open file: ${openRes.status}`);
  325. }
  326. const getattrRes = openResponse.resarray[openResponse.resarray.length - 1];
  327. if (getattrRes.status !== 0 || !getattrRes.resok) {
  328. throw new Error(`Failed to get attributes: ${getattrRes.status}`);
  329. }
  330. const fattr = getattrRes.resok.objAttributes;
  331. const reader = new Reader_1.Reader();
  332. reader.reset(fattr.attrVals);
  333. const xdr = new XdrDecoder_1.XdrDecoder(reader);
  334. const currentSize = Number(xdr.readUnsignedHyper());
  335. const openStateid = openRes.resok.stateid;
  336. const buffer = this.encodeData(data);
  337. const chunkSize = 65536;
  338. try {
  339. let offset = BigInt(currentSize);
  340. for (let i = 0; i < buffer.length; i += chunkSize) {
  341. const chunk = buffer.slice(i, Math.min(i + chunkSize, buffer.length));
  342. const writeResponse = await this.fs.compound([
  343. builder_1.nfs.WRITE(openStateid, offset, 2, chunk),
  344. ]);
  345. if (writeResponse.status !== 0) {
  346. throw new Error(`Failed to write file: ${writeResponse.status}`);
  347. }
  348. const writeRes = writeResponse.resarray[0];
  349. if (writeRes.status !== 0 || !writeRes.resok) {
  350. throw new Error(`Failed to write file: ${writeRes.status}`);
  351. }
  352. offset += BigInt(writeRes.resok.count);
  353. }
  354. }
  355. finally {
  356. await this.closeStateid(openOwner, openStateid);
  357. }
  358. };
  359. this.truncate = async (path, len = 0) => {
  360. const pathStr = typeof path === 'string' ? path : path.toString();
  361. const parts = this.parsePath(pathStr);
  362. const operations = this.navigateToPath(parts);
  363. const writer = new Writer_1.Writer(16);
  364. const xdr = new XdrEncoder_1.XdrEncoder(writer);
  365. xdr.writeUnsignedHyper(BigInt(len));
  366. const attrVals = writer.flush();
  367. const sizeAttrs = builder_1.nfs.Fattr([4], attrVals);
  368. const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
  369. operations.push(builder_1.nfs.SETATTR(stateid, sizeAttrs));
  370. const response = await this.fs.compound(operations);
  371. if (response.status !== 0) {
  372. throw new Error(`Failed to truncate file: ${response.status}`);
  373. }
  374. const setattrRes = response.resarray[response.resarray.length - 1];
  375. if (setattrRes.status !== 0) {
  376. throw new Error(`Failed to truncate file: ${setattrRes.status}`);
  377. }
  378. };
  379. this.unlink = async (path) => {
  380. const pathStr = typeof path === 'string' ? path : path.toString();
  381. const parts = this.parsePath(pathStr);
  382. if (parts.length === 0) {
  383. throw new Error('Cannot unlink root directory');
  384. }
  385. const operations = this.navigateToParent(parts);
  386. const filename = parts[parts.length - 1];
  387. operations.push(builder_1.nfs.REMOVE(filename));
  388. const response = await this.fs.compound(operations);
  389. if (response.status !== 0) {
  390. throw new Error(`Failed to unlink file: ${response.status}`);
  391. }
  392. const removeRes = response.resarray[response.resarray.length - 1];
  393. if (removeRes.status !== 0) {
  394. throw new Error(`Failed to unlink file: ${removeRes.status}`);
  395. }
  396. };
  397. this.rmdir = async (path, options) => {
  398. const pathStr = typeof path === 'string' ? path : path.toString();
  399. const parts = this.parsePath(pathStr);
  400. if (parts.length === 0) {
  401. throw new Error('Cannot remove root directory');
  402. }
  403. const operations = this.navigateToParent(parts);
  404. const dirname = parts[parts.length - 1];
  405. operations.push(builder_1.nfs.REMOVE(dirname));
  406. const response = await this.fs.compound(operations);
  407. if (response.status !== 0) {
  408. throw new Error(`Failed to remove directory: ${response.status}`);
  409. }
  410. const removeRes = response.resarray[response.resarray.length - 1];
  411. if (removeRes.status !== 0) {
  412. throw new Error(`Failed to remove directory: ${removeRes.status}`);
  413. }
  414. };
  415. this.rm = async (path, options) => {
  416. const pathStr = typeof path === 'string' ? path : path.toString();
  417. const parts = this.parsePath(pathStr);
  418. if (parts.length === 0) {
  419. throw new Error('Cannot remove root directory');
  420. }
  421. const force = options?.force ?? false;
  422. const recursive = options?.recursive ?? false;
  423. if (recursive) {
  424. try {
  425. const stats = await this.stat(path);
  426. if (stats.isDirectory()) {
  427. const entries = await this.readdir(path);
  428. for (const entry of entries) {
  429. const entryPath = pathStr + '/' + entry;
  430. await this.rm(entryPath, options);
  431. }
  432. }
  433. }
  434. catch (err) {
  435. if (!force)
  436. throw err;
  437. return;
  438. }
  439. }
  440. try {
  441. const operations = this.navigateToParent(parts);
  442. const name = parts[parts.length - 1];
  443. operations.push(builder_1.nfs.REMOVE(name));
  444. const response = await this.fs.compound(operations);
  445. if (response.status !== 0) {
  446. if (!force)
  447. throw new Error(`Failed to remove: ${response.status}`);
  448. return;
  449. }
  450. const removeRes = response.resarray[response.resarray.length - 1];
  451. if (removeRes.status !== 0) {
  452. if (!force)
  453. throw new Error(`Failed to remove: ${removeRes.status}`);
  454. }
  455. }
  456. catch (err) {
  457. if (!force)
  458. throw err;
  459. }
  460. };
  461. this.access = async (path, mode = 0) => {
  462. const pathStr = typeof path === 'string' ? path : path.toString();
  463. const parts = this.parsePath(pathStr);
  464. const operations = this.navigateToPath(parts);
  465. let accessMask = 0;
  466. if (mode === 0) {
  467. accessMask = 1;
  468. }
  469. else {
  470. if (mode & 4)
  471. accessMask |= 1;
  472. if (mode & 2)
  473. accessMask |= 4;
  474. if (mode & 1)
  475. accessMask |= 32;
  476. }
  477. operations.push(builder_1.nfs.ACCESS(accessMask));
  478. const response = await this.fs.compound(operations);
  479. if (response.status !== 0) {
  480. throw new Error(`Access denied: ${response.status}`);
  481. }
  482. const accessRes = response.resarray[response.resarray.length - 1];
  483. if (accessRes.status !== 0) {
  484. throw new Error(`Access denied: ${accessRes.status}`);
  485. }
  486. };
  487. this.rename = async (oldPath, newPath) => {
  488. const oldPathStr = typeof oldPath === 'string' ? oldPath : oldPath.toString();
  489. const newPathStr = typeof newPath === 'string' ? newPath : newPath.toString();
  490. const oldParts = this.parsePath(oldPathStr);
  491. const newParts = this.parsePath(newPathStr);
  492. if (oldParts.length === 0 || newParts.length === 0) {
  493. throw new Error('Cannot rename root directory');
  494. }
  495. const operations = [];
  496. operations.push(builder_1.nfs.PUTROOTFH());
  497. for (const part of oldParts.slice(0, -1)) {
  498. operations.push(builder_1.nfs.LOOKUP(part));
  499. }
  500. operations.push(builder_1.nfs.SAVEFH());
  501. operations.push(builder_1.nfs.PUTROOTFH());
  502. for (const part of newParts.slice(0, -1)) {
  503. operations.push(builder_1.nfs.LOOKUP(part));
  504. }
  505. const oldname = oldParts[oldParts.length - 1];
  506. const newname = newParts[newParts.length - 1];
  507. operations.push(builder_1.nfs.RENAME(oldname, newname));
  508. const response = await this.fs.compound(operations);
  509. if (response.status !== 0) {
  510. throw new Error(`Failed to rename: ${response.status}`);
  511. }
  512. const renameRes = response.resarray[response.resarray.length - 1];
  513. if (renameRes.status !== 0) {
  514. throw new Error(`Failed to rename: ${renameRes.status}`);
  515. }
  516. };
  517. this.copyFile = async (src, dest, flags) => {
  518. const data = await this.readFile(src);
  519. await this.writeFile(dest, data);
  520. };
  521. this.realpath = async (path, options) => {
  522. const encoding = typeof options === 'string' ? options : options?.encoding;
  523. const pathStr = typeof path === 'string' ? path : path.toString();
  524. const normalized = '/' + this.parsePath(pathStr).join('/');
  525. if (!encoding || encoding === 'utf8') {
  526. return normalized;
  527. }
  528. return Buffer.from(normalized, 'utf8');
  529. };
  530. this.link = async (existingPath, newPath) => {
  531. const existingPathStr = typeof existingPath === 'string' ? existingPath : existingPath.toString();
  532. const newPathStr = typeof newPath === 'string' ? newPath : newPath.toString();
  533. const existingParts = this.parsePath(existingPathStr);
  534. const newParts = this.parsePath(newPathStr);
  535. if (newParts.length === 0) {
  536. throw new Error('Cannot create link at root');
  537. }
  538. const operations = this.navigateToPath(existingParts);
  539. operations.push(builder_1.nfs.SAVEFH());
  540. operations.push(builder_1.nfs.PUTROOTFH());
  541. for (const part of newParts.slice(0, -1)) {
  542. operations.push(builder_1.nfs.LOOKUP(part));
  543. }
  544. const newname = newParts[newParts.length - 1];
  545. operations.push(builder_1.nfs.LINK(newname));
  546. const response = await this.fs.compound(operations);
  547. if (response.status !== 0) {
  548. throw new Error(`Failed to create link: ${response.status}`);
  549. }
  550. const linkRes = response.resarray[response.resarray.length - 1];
  551. if (linkRes.status !== 0) {
  552. throw new Error(`Failed to create link: ${linkRes.status}`);
  553. }
  554. };
  555. this.symlink = async (target, path, type) => {
  556. const targetStr = typeof target === 'string' ? target : target.toString();
  557. const pathStr = typeof path === 'string' ? path : path.toString();
  558. const parts = this.parsePath(pathStr);
  559. if (parts.length === 0) {
  560. throw new Error('Cannot create symlink at root');
  561. }
  562. const operations = this.navigateToParent(parts);
  563. const linkname = parts[parts.length - 1];
  564. const createType = new structs.Nfsv4CreateType(5, new structs.Nfsv4CreateTypeLink(targetStr));
  565. const emptyAttrs = builder_1.nfs.Fattr([], new Uint8Array(0));
  566. operations.push(builder_1.nfs.CREATE(createType, linkname, emptyAttrs));
  567. const response = await this.fs.compound(operations);
  568. if (response.status !== 0) {
  569. throw new Error(`Failed to create symlink: ${response.status}`);
  570. }
  571. const createRes = response.resarray[response.resarray.length - 1];
  572. if (createRes.status !== 0) {
  573. throw new Error(`Failed to create symlink: ${createRes.status}`);
  574. }
  575. };
  576. this.utimes = async (path, atime, mtime) => {
  577. const pathStr = typeof path === 'string' ? path : path.toString();
  578. const parts = this.parsePath(pathStr);
  579. const operations = this.navigateToPath(parts);
  580. const atimeMs = typeof atime === 'number' ? atime : atime instanceof Date ? atime.getTime() : Date.now();
  581. const mtimeMs = typeof mtime === 'number' ? mtime : mtime instanceof Date ? mtime.getTime() : Date.now();
  582. const writer = new Writer_1.Writer(64);
  583. const xdr = new XdrEncoder_1.XdrEncoder(writer);
  584. xdr.writeUnsignedInt(1);
  585. xdr.writeHyper(BigInt(Math.floor(atimeMs / 1000)));
  586. xdr.writeUnsignedInt((atimeMs % 1000) * 1000000);
  587. xdr.writeUnsignedInt(1);
  588. xdr.writeHyper(BigInt(Math.floor(mtimeMs / 1000)));
  589. xdr.writeUnsignedInt((mtimeMs % 1000) * 1000000);
  590. const attrVals = writer.flush();
  591. const timeAttrs = builder_1.nfs.Fattr([48, 54], attrVals);
  592. const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
  593. operations.push(builder_1.nfs.SETATTR(stateid, timeAttrs));
  594. const response = await this.fs.compound(operations);
  595. if (response.status !== 0) {
  596. throw new Error(`Failed to set times: ${response.status}`);
  597. }
  598. const setattrRes = response.resarray[response.resarray.length - 1];
  599. if (setattrRes.status !== 0) {
  600. throw new Error(`Failed to set times: ${setattrRes.status}`);
  601. }
  602. };
  603. this.readlink = async (path, options) => {
  604. const encoding = typeof options === 'string' ? options : options?.encoding;
  605. const pathStr = typeof path === 'string' ? path : path.toString();
  606. const parts = this.parsePath(pathStr);
  607. const operations = this.navigateToPath(parts);
  608. operations.push(builder_1.nfs.READLINK());
  609. const response = await this.fs.compound(operations);
  610. if (response.status !== 0) {
  611. throw new Error(`Failed to read link: ${response.status}`);
  612. }
  613. const readlinkRes = response.resarray[response.resarray.length - 1];
  614. if (readlinkRes.status !== 0 || !readlinkRes.resok) {
  615. throw new Error(`Failed to read link: ${readlinkRes.status}`);
  616. }
  617. if (!encoding || encoding === 'utf8') {
  618. return readlinkRes.resok.link;
  619. }
  620. return Buffer.from(readlinkRes.resok.link, 'utf8');
  621. };
  622. this.opendir = async (path, options) => {
  623. const pathStr = typeof path === 'string' ? path : path.toString();
  624. const parts = this.parsePath(pathStr);
  625. const operations = this.navigateToPath(parts);
  626. return new NfsFsDir_1.NfsFsDir(pathStr, this.fs, operations);
  627. };
  628. this.mkdtemp = async (prefix, options) => {
  629. const encoding = typeof options === 'string' ? options : options?.encoding;
  630. const randomSuffix = Math.random().toString(36).substring(2, 8);
  631. const dirName = prefix + randomSuffix;
  632. await this.mkdir(dirName);
  633. if (!encoding || encoding === 'utf8')
  634. return dirName;
  635. return Buffer.from(dirName, 'utf8');
  636. };
  637. this.chmod = async (path, mode) => {
  638. const pathStr = typeof path === 'string' ? path : path.toString();
  639. const parts = this.parsePath(pathStr);
  640. const operations = this.navigateToPath(parts);
  641. const modeValue = typeof mode === 'number' ? mode : parseInt(mode.toString(), 8);
  642. const writer = new Writer_1.Writer(8);
  643. const xdr = new XdrEncoder_1.XdrEncoder(writer);
  644. xdr.writeUnsignedInt(modeValue);
  645. const attrVals = writer.flush();
  646. const attrs = builder_1.nfs.Fattr([33], attrVals);
  647. const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
  648. operations.push(builder_1.nfs.SETATTR(stateid, attrs));
  649. const response = await this.fs.compound(operations);
  650. if (response.status !== 0) {
  651. throw new Error(`Failed to chmod: ${response.status}`);
  652. }
  653. const setattrRes = response.resarray[response.resarray.length - 1];
  654. if (setattrRes.status !== 0) {
  655. throw new Error(`Failed to chmod: ${setattrRes.status}`);
  656. }
  657. };
  658. this.chown = async (path, uid, gid) => {
  659. const pathStr = typeof path === 'string' ? path : path.toString();
  660. const parts = this.parsePath(pathStr);
  661. const operations = this.navigateToPath(parts);
  662. const writer = new Writer_1.Writer(64);
  663. const xdr = new XdrEncoder_1.XdrEncoder(writer);
  664. xdr.writeStr(uid.toString());
  665. xdr.writeStr(gid.toString());
  666. const attrVals = writer.flush();
  667. const attrs = builder_1.nfs.Fattr([36, 37], attrVals);
  668. const stateid = builder_1.nfs.Stateid(0, new Uint8Array(12));
  669. operations.push(builder_1.nfs.SETATTR(stateid, attrs));
  670. const response = await this.fs.compound(operations);
  671. if (response.status !== 0) {
  672. throw new Error(`Failed to chown: ${response.status}`);
  673. }
  674. const setattrRes = response.resarray[response.resarray.length - 1];
  675. if (setattrRes.status !== 0) {
  676. throw new Error(`Failed to chown: ${setattrRes.status}`);
  677. }
  678. };
  679. this.lchmod = async (path, mode) => {
  680. return this.chmod(path, mode);
  681. };
  682. this.lchown = async (path, uid, gid) => {
  683. return this.chown(path, uid, gid);
  684. };
  685. this.lutimes = async (path, atime, mtime) => {
  686. return this.utimes(path, atime, mtime);
  687. };
  688. this.open = async (path, flags, mode) => {
  689. const pathStr = typeof path === 'string' ? path : path.toString();
  690. const parts = this.parsePath(pathStr);
  691. const operations = this.navigateToParent(parts);
  692. const filename = parts[parts.length - 1];
  693. const openOwner = this.createDefaultOpenOwner();
  694. const claim = builder_1.nfs.OpenClaimNull(filename);
  695. let access = 1;
  696. const openSeqid = this.nextOpenOwnerSeqid(openOwner);
  697. if (typeof flags === 'string') {
  698. if (flags.includes('r') && flags.includes('+')) {
  699. access = 3;
  700. }
  701. else if (flags.includes('w') || flags.includes('a')) {
  702. access = 2;
  703. if (flags.includes('+')) {
  704. access = 3;
  705. }
  706. }
  707. }
  708. else if (typeof flags === 'number') {
  709. const O_RDONLY = 0;
  710. const O_WRONLY = 1;
  711. const O_RDWR = 2;
  712. const O_ACCMODE = 3;
  713. const accessMode = flags & O_ACCMODE;
  714. switch (accessMode) {
  715. case O_RDONLY:
  716. access = 1;
  717. break;
  718. case O_WRONLY:
  719. access = 2;
  720. break;
  721. case O_RDWR:
  722. access = 3;
  723. break;
  724. }
  725. }
  726. operations.push(builder_1.nfs.OPEN(openSeqid, access, 0, openOwner, builder_1.nfs.OpenHowNoCreate(), claim));
  727. const openResponse = await this.fs.compound(operations);
  728. if (openResponse.status !== 0) {
  729. throw new Error(`Failed to open file: ${openResponse.status}`);
  730. }
  731. const openRes = openResponse.resarray[openResponse.resarray.length - 1];
  732. if (openRes.status !== 0 || !openRes.resok) {
  733. throw new Error(`Failed to open file: ${openRes.status}`);
  734. }
  735. const stateid = openRes.resok.stateid;
  736. const fd = Math.floor(Math.random() * 1000000);
  737. return new NfsFsFileHandle_1.NfsFsFileHandle(fd, pathStr, this, stateid, openOwner);
  738. };
  739. this.statfs = (path, options) => {
  740. throw new Error('Not implemented.');
  741. };
  742. this.watch = (filename, options) => {
  743. throw new Error('Not implemented.');
  744. };
  745. this.glob = (pattern, options) => {
  746. throw new Error('Not implemented.');
  747. };
  748. }
  749. makeOpenOwnerKey(owner) {
  750. return `${owner.clientid}:${Buffer.from(owner.owner).toString('hex')}`;
  751. }
  752. nextOpenOwnerSeqid(owner) {
  753. const key = this.makeOpenOwnerKey(owner);
  754. const last = this.openOwnerSeqids.get(key);
  755. const next = last === undefined ? 0 : last === 0xffffffff ? 1 : (last + 1) >>> 0;
  756. this.openOwnerSeqids.set(key, next);
  757. return next;
  758. }
  759. createDefaultOpenOwner() {
  760. return builder_1.nfs.OpenOwner(BigInt(1), new Uint8Array(this.defaultOpenOwnerId));
  761. }
  762. attrNumsToBitmap(attrNums) {
  763. const bitmap = [];
  764. for (const attrNum of attrNums) {
  765. const wordIndex = Math.floor(attrNum / 32);
  766. const bitIndex = attrNum % 32;
  767. while (bitmap.length <= wordIndex) {
  768. bitmap.push(0);
  769. }
  770. bitmap[wordIndex] |= 1 << bitIndex;
  771. }
  772. return bitmap;
  773. }
  774. parsePath(path) {
  775. const normalized = path.replace(/^\/+/, '').replace(/\/+$/, '');
  776. if (!normalized)
  777. return [];
  778. return normalized.split('/').filter((part) => part.length > 0);
  779. }
  780. navigateToParent(parts) {
  781. const operations = [builder_1.nfs.PUTROOTFH()];
  782. for (const part of parts.slice(0, -1)) {
  783. operations.push(builder_1.nfs.LOOKUP(part));
  784. }
  785. return operations;
  786. }
  787. navigateToPath(parts) {
  788. const operations = [builder_1.nfs.PUTROOTFH()];
  789. for (const part of parts) {
  790. operations.push(builder_1.nfs.LOOKUP(part));
  791. }
  792. return operations;
  793. }
  794. encodeData(data) {
  795. if (data instanceof Uint8Array)
  796. return data;
  797. if (data instanceof ArrayBuffer)
  798. return new Uint8Array(data);
  799. if (typeof data === 'string')
  800. return new TextEncoder().encode(data);
  801. if (Buffer.isBuffer(data))
  802. return new Uint8Array(data);
  803. throw new Error('Unsupported data type');
  804. }
  805. decodeData(data, encoding) {
  806. if (!encoding || encoding === 'buffer')
  807. return Buffer.from(data);
  808. return new TextDecoder(encoding).decode(data);
  809. }
  810. }
  811. exports.Nfsv4FsClient = Nfsv4FsClient;
  812. //# sourceMappingURL=Nfsv4FsClient.js.map