• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  MathMax,
5  MathMin,
6  NumberIsSafeInteger,
7  Symbol,
8} = primordials;
9
10const {
11  F_OK,
12  O_SYMLINK,
13  O_WRONLY,
14  S_IFMT,
15  S_IFREG
16} = internalBinding('constants').fs;
17const binding = internalBinding('fs');
18const { Buffer, kMaxLength } = require('buffer');
19const {
20  ERR_FS_FILE_TOO_LARGE,
21  ERR_INVALID_ARG_TYPE,
22  ERR_INVALID_ARG_VALUE,
23  ERR_METHOD_NOT_IMPLEMENTED
24} = require('internal/errors').codes;
25const { isUint8Array } = require('internal/util/types');
26const { rimrafPromises } = require('internal/fs/rimraf');
27const {
28  copyObject,
29  getDirents,
30  getOptions,
31  getStatsFromBinding,
32  getValidatedPath,
33  nullCheck,
34  preprocessSymlinkDestination,
35  stringToFlags,
36  stringToSymlinkType,
37  toUnixTimestamp,
38  validateBufferArray,
39  validateOffsetLengthRead,
40  validateOffsetLengthWrite,
41  validateRmdirOptions,
42  warnOnNonPortableTemplate
43} = require('internal/fs/utils');
44const { opendir } = require('internal/fs/dir');
45const {
46  parseMode,
47  validateBuffer,
48  validateInteger,
49  validateUint32
50} = require('internal/validators');
51const pathModule = require('path');
52const { promisify } = require('internal/util');
53
54const kHandle = Symbol('kHandle');
55const kFd = Symbol('kFd');
56const { kUsePromises } = binding;
57const {
58  JSTransferable, kDeserialize, kTransfer, kTransferList
59} = require('internal/worker/js_transferable');
60
61const getDirectoryEntriesPromise = promisify(getDirents);
62
63class FileHandle extends JSTransferable {
64  constructor(filehandle) {
65    super();
66    this[kHandle] = filehandle;
67    this[kFd] = filehandle ? filehandle.fd : -1;
68  }
69
70  getAsyncId() {
71    return this[kHandle].getAsyncId();
72  }
73
74  get fd() {
75    return this[kFd];
76  }
77
78  appendFile(data, options) {
79    return writeFile(this, data, options);
80  }
81
82  chmod(mode) {
83    return fchmod(this, mode);
84  }
85
86  chown(uid, gid) {
87    return fchown(this, uid, gid);
88  }
89
90  datasync() {
91    return fdatasync(this);
92  }
93
94  sync() {
95    return fsync(this);
96  }
97
98  read(buffer, offset, length, position) {
99    return read(this, buffer, offset, length, position);
100  }
101
102  readv(buffers, position) {
103    return readv(this, buffers, position);
104  }
105
106  readFile(options) {
107    return readFile(this, options);
108  }
109
110  stat(options) {
111    return fstat(this, options);
112  }
113
114  truncate(len = 0) {
115    return ftruncate(this, len);
116  }
117
118  utimes(atime, mtime) {
119    return futimes(this, atime, mtime);
120  }
121
122  write(buffer, offset, length, position) {
123    return write(this, buffer, offset, length, position);
124  }
125
126  writev(buffers, position) {
127    return writev(this, buffers, position);
128  }
129
130  writeFile(data, options) {
131    return writeFile(this, data, options);
132  }
133
134  close = () => {
135    this[kFd] = -1;
136    return this[kHandle].close();
137  }
138
139  [kTransfer]() {
140    const handle = this[kHandle];
141    this[kFd] = -1;
142    this[kHandle] = null;
143
144    return {
145      data: { handle },
146      deserializeInfo: 'internal/fs/promises:FileHandle'
147    };
148  }
149
150  [kTransferList]() {
151    return [ this[kHandle] ];
152  }
153
154  [kDeserialize]({ handle }) {
155    this[kHandle] = handle;
156    this[kFd] = handle.fd;
157  }
158}
159
160function validateFileHandle(handle) {
161  if (!(handle instanceof FileHandle))
162    throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle);
163}
164
165async function writeFileHandle(filehandle, data, options) {
166  let buffer = isUint8Array(data) ?
167    data : Buffer.from('' + data, options.encoding || 'utf8');
168  let remaining = buffer.length;
169  if (remaining === 0) return;
170  do {
171    const { bytesWritten } =
172      await write(filehandle, buffer, 0,
173                  MathMin(16384, buffer.length));
174    remaining -= bytesWritten;
175    buffer = buffer.slice(bytesWritten);
176  } while (remaining > 0);
177}
178
179// Note: This is different from kReadFileBufferLength used for non-promisified
180// fs.readFile.
181const kReadFileMaxChunkSize = 16384;
182
183async function readFileHandle(filehandle, options) {
184  const statFields = await binding.fstat(filehandle.fd, false, kUsePromises);
185
186  let size;
187  if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
188    size = statFields[8/* size */];
189  } else {
190    size = 0;
191  }
192
193  if (size > kMaxLength)
194    throw new ERR_FS_FILE_TOO_LARGE(size);
195
196  const chunks = [];
197  const chunkSize = size === 0 ?
198    kReadFileMaxChunkSize :
199    MathMin(size, kReadFileMaxChunkSize);
200  let endOfFile = false;
201  do {
202    const buf = Buffer.alloc(chunkSize);
203    const { bytesRead, buffer } =
204      await read(filehandle, buf, 0, chunkSize, -1);
205    endOfFile = bytesRead === 0;
206    if (bytesRead > 0)
207      chunks.push(buffer.slice(0, bytesRead));
208  } while (!endOfFile);
209
210  const result = Buffer.concat(chunks);
211
212  return options.encoding ? result.toString(options.encoding) : result;
213}
214
215// All of the functions are defined as async in order to ensure that errors
216// thrown cause promise rejections rather than being thrown synchronously.
217async function access(path, mode = F_OK) {
218  path = getValidatedPath(path);
219
220  mode = mode | 0;
221  return binding.access(pathModule.toNamespacedPath(path), mode,
222                        kUsePromises);
223}
224
225async function copyFile(src, dest, flags) {
226  src = getValidatedPath(src, 'src');
227  dest = getValidatedPath(dest, 'dest');
228  flags = flags | 0;
229  return binding.copyFile(pathModule.toNamespacedPath(src),
230                          pathModule.toNamespacedPath(dest),
231                          flags, kUsePromises);
232}
233
234// Note that unlike fs.open() which uses numeric file descriptors,
235// fsPromises.open() uses the fs.FileHandle class.
236async function open(path, flags, mode) {
237  path = getValidatedPath(path);
238  if (arguments.length < 2) flags = 'r';
239  const flagsNumber = stringToFlags(flags);
240  mode = parseMode(mode, 'mode', 0o666);
241  return new FileHandle(
242    await binding.openFileHandle(pathModule.toNamespacedPath(path),
243                                 flagsNumber, mode, kUsePromises));
244}
245
246async function read(handle, buffer, offset, length, position) {
247  validateFileHandle(handle);
248  validateBuffer(buffer);
249
250  offset |= 0;
251  length |= 0;
252
253  if (length === 0)
254    return { bytesRead: length, buffer };
255
256  if (buffer.length === 0) {
257    throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
258                                    'is empty and cannot be written');
259  }
260
261  validateOffsetLengthRead(offset, length, buffer.length);
262
263  if (!NumberIsSafeInteger(position))
264    position = -1;
265
266  const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
267                                        position, kUsePromises)) || 0;
268
269  return { bytesRead, buffer };
270}
271
272async function readv(handle, buffers, position) {
273  validateFileHandle(handle);
274  validateBufferArray(buffers);
275
276  if (typeof position !== 'number')
277    position = null;
278
279  const bytesRead = (await binding.readBuffers(handle.fd, buffers, position,
280                                               kUsePromises)) || 0;
281  return { bytesRead, buffers };
282}
283
284async function write(handle, buffer, offset, length, position) {
285  validateFileHandle(handle);
286
287  if (buffer.length === 0)
288    return { bytesWritten: 0, buffer };
289
290  if (isUint8Array(buffer)) {
291    if (typeof offset !== 'number')
292      offset = 0;
293    if (typeof length !== 'number')
294      length = buffer.length - offset;
295    if (typeof position !== 'number')
296      position = null;
297    validateOffsetLengthWrite(offset, length, buffer.byteLength);
298    const bytesWritten =
299      (await binding.writeBuffer(handle.fd, buffer, offset,
300                                 length, position, kUsePromises)) || 0;
301    return { bytesWritten, buffer };
302  }
303
304  if (typeof buffer !== 'string')
305    buffer += '';
306  const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
307                                                  length, kUsePromises)) || 0;
308  return { bytesWritten, buffer };
309}
310
311async function writev(handle, buffers, position) {
312  validateFileHandle(handle);
313  validateBufferArray(buffers);
314
315  if (typeof position !== 'number')
316    position = null;
317
318  const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position,
319                                                   kUsePromises)) || 0;
320  return { bytesWritten, buffers };
321}
322
323async function rename(oldPath, newPath) {
324  oldPath = getValidatedPath(oldPath, 'oldPath');
325  newPath = getValidatedPath(newPath, 'newPath');
326  return binding.rename(pathModule.toNamespacedPath(oldPath),
327                        pathModule.toNamespacedPath(newPath),
328                        kUsePromises);
329}
330
331async function truncate(path, len = 0) {
332  const fd = await open(path, 'r+');
333  return ftruncate(fd, len).finally(fd.close.bind(fd));
334}
335
336async function ftruncate(handle, len = 0) {
337  validateFileHandle(handle);
338  validateInteger(len, 'len');
339  len = MathMax(0, len);
340  return binding.ftruncate(handle.fd, len, kUsePromises);
341}
342
343async function rmdir(path, options) {
344  path = pathModule.toNamespacedPath(getValidatedPath(path));
345  options = validateRmdirOptions(options);
346
347  if (options.recursive) {
348    return rimrafPromises(path, options);
349  }
350
351  return binding.rmdir(path, kUsePromises);
352}
353
354async function fdatasync(handle) {
355  validateFileHandle(handle);
356  return binding.fdatasync(handle.fd, kUsePromises);
357}
358
359async function fsync(handle) {
360  validateFileHandle(handle);
361  return binding.fsync(handle.fd, kUsePromises);
362}
363
364async function mkdir(path, options) {
365  if (typeof options === 'number' || typeof options === 'string') {
366    options = { mode: options };
367  }
368  const {
369    recursive = false,
370    mode = 0o777
371  } = options || {};
372  path = getValidatedPath(path);
373  if (typeof recursive !== 'boolean')
374    throw new ERR_INVALID_ARG_TYPE('options.recursive', 'boolean', recursive);
375
376  return binding.mkdir(pathModule.toNamespacedPath(path),
377                       parseMode(mode, 'mode', 0o777), recursive,
378                       kUsePromises);
379}
380
381async function readdir(path, options) {
382  options = getOptions(options, {});
383  path = getValidatedPath(path);
384  const result = await binding.readdir(pathModule.toNamespacedPath(path),
385                                       options.encoding,
386                                       !!options.withFileTypes,
387                                       kUsePromises);
388  return options.withFileTypes ?
389    getDirectoryEntriesPromise(path, result) :
390    result;
391}
392
393async function readlink(path, options) {
394  options = getOptions(options, {});
395  path = getValidatedPath(path, 'oldPath');
396  return binding.readlink(pathModule.toNamespacedPath(path),
397                          options.encoding, kUsePromises);
398}
399
400async function symlink(target, path, type_) {
401  const type = (typeof type_ === 'string' ? type_ : null);
402  target = getValidatedPath(target, 'target');
403  path = getValidatedPath(path);
404  return binding.symlink(preprocessSymlinkDestination(target, type, path),
405                         pathModule.toNamespacedPath(path),
406                         stringToSymlinkType(type),
407                         kUsePromises);
408}
409
410async function fstat(handle, options = { bigint: false }) {
411  validateFileHandle(handle);
412  const result = await binding.fstat(handle.fd, options.bigint, kUsePromises);
413  return getStatsFromBinding(result);
414}
415
416async function lstat(path, options = { bigint: false }) {
417  path = getValidatedPath(path);
418  const result = await binding.lstat(pathModule.toNamespacedPath(path),
419                                     options.bigint, kUsePromises);
420  return getStatsFromBinding(result);
421}
422
423async function stat(path, options = { bigint: false }) {
424  path = getValidatedPath(path);
425  const result = await binding.stat(pathModule.toNamespacedPath(path),
426                                    options.bigint, kUsePromises);
427  return getStatsFromBinding(result);
428}
429
430async function link(existingPath, newPath) {
431  existingPath = getValidatedPath(existingPath, 'existingPath');
432  newPath = getValidatedPath(newPath, 'newPath');
433  return binding.link(pathModule.toNamespacedPath(existingPath),
434                      pathModule.toNamespacedPath(newPath),
435                      kUsePromises);
436}
437
438async function unlink(path) {
439  path = getValidatedPath(path);
440  return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
441}
442
443async function fchmod(handle, mode) {
444  validateFileHandle(handle);
445  mode = parseMode(mode, 'mode');
446  return binding.fchmod(handle.fd, mode, kUsePromises);
447}
448
449async function chmod(path, mode) {
450  path = getValidatedPath(path);
451  mode = parseMode(mode, 'mode');
452  return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
453}
454
455async function lchmod(path, mode) {
456  if (O_SYMLINK === undefined)
457    throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()');
458
459  const fd = await open(path, O_WRONLY | O_SYMLINK);
460  return fchmod(fd, mode).finally(fd.close);
461}
462
463async function lchown(path, uid, gid) {
464  path = getValidatedPath(path);
465  validateUint32(uid, 'uid');
466  validateUint32(gid, 'gid');
467  return binding.lchown(pathModule.toNamespacedPath(path),
468                        uid, gid, kUsePromises);
469}
470
471async function fchown(handle, uid, gid) {
472  validateFileHandle(handle);
473  validateUint32(uid, 'uid');
474  validateUint32(gid, 'gid');
475  return binding.fchown(handle.fd, uid, gid, kUsePromises);
476}
477
478async function chown(path, uid, gid) {
479  path = getValidatedPath(path);
480  validateUint32(uid, 'uid');
481  validateUint32(gid, 'gid');
482  return binding.chown(pathModule.toNamespacedPath(path),
483                       uid, gid, kUsePromises);
484}
485
486async function utimes(path, atime, mtime) {
487  path = getValidatedPath(path);
488  return binding.utimes(pathModule.toNamespacedPath(path),
489                        toUnixTimestamp(atime),
490                        toUnixTimestamp(mtime),
491                        kUsePromises);
492}
493
494async function futimes(handle, atime, mtime) {
495  validateFileHandle(handle);
496  atime = toUnixTimestamp(atime, 'atime');
497  mtime = toUnixTimestamp(mtime, 'mtime');
498  return binding.futimes(handle.fd, atime, mtime, kUsePromises);
499}
500
501async function lutimes(path, atime, mtime) {
502  path = getValidatedPath(path);
503  return binding.lutimes(pathModule.toNamespacedPath(path),
504                         toUnixTimestamp(atime),
505                         toUnixTimestamp(mtime),
506                         kUsePromises);
507}
508
509async function realpath(path, options) {
510  options = getOptions(options, {});
511  path = getValidatedPath(path);
512  return binding.realpath(path, options.encoding, kUsePromises);
513}
514
515async function mkdtemp(prefix, options) {
516  options = getOptions(options, {});
517  if (!prefix || typeof prefix !== 'string') {
518    throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
519  }
520  nullCheck(prefix);
521  warnOnNonPortableTemplate(prefix);
522  return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
523}
524
525async function writeFile(path, data, options) {
526  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
527  const flag = options.flag || 'w';
528
529  if (path instanceof FileHandle)
530    return writeFileHandle(path, data, options);
531
532  const fd = await open(path, flag, options.mode);
533  return writeFileHandle(fd, data, options).finally(fd.close);
534}
535
536async function appendFile(path, data, options) {
537  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
538  options = copyObject(options);
539  options.flag = options.flag || 'a';
540  return writeFile(path, data, options);
541}
542
543async function readFile(path, options) {
544  options = getOptions(options, { flag: 'r' });
545  const flag = options.flag || 'r';
546
547  if (path instanceof FileHandle)
548    return readFileHandle(path, options);
549
550  const fd = await open(path, flag, 0o666);
551  return readFileHandle(fd, options).finally(fd.close);
552}
553
554module.exports = {
555  exports: {
556    access,
557    copyFile,
558    open,
559    opendir: promisify(opendir),
560    rename,
561    truncate,
562    rmdir,
563    mkdir,
564    readdir,
565    readlink,
566    symlink,
567    lstat,
568    stat,
569    link,
570    unlink,
571    chmod,
572    lchmod,
573    lchown,
574    chown,
575    utimes,
576    lutimes,
577    realpath,
578    mkdtemp,
579    writeFile,
580    appendFile,
581    readFile,
582  },
583
584  FileHandle
585};
586