fs.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. module.metadata = {
  6. "stability": "experimental"
  7. };
  8. const { Cc, Ci, CC } = require("chrome");
  9. const { setTimeout } = require("../timers");
  10. const { Stream, InputStream, OutputStream } = require("./stream");
  11. const { emit, on } = require("../event/core");
  12. const { Buffer } = require("./buffer");
  13. const { ns } = require("../core/namespace");
  14. const { Class } = require("../core/heritage");
  15. const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
  16. "initWithPath");
  17. const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
  18. "nsIFileOutputStream", "init");
  19. const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
  20. "nsIFileInputStream", "init");
  21. const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
  22. "nsIBinaryInputStream", "setInputStream");
  23. const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
  24. "nsIBinaryOutputStream", "setOutputStream");
  25. const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
  26. "nsIInputStreamPump", "init");
  27. const { createOutputTransport, createInputTransport } =
  28. Cc["@mozilla.org/network/stream-transport-service;1"].
  29. getService(Ci.nsIStreamTransportService);
  30. const { OPEN_UNBUFFERED } = Ci.nsITransport;
  31. const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
  32. const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
  33. const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;
  34. const FILE_PERMISSION = parseInt("0666", 8);
  35. const PR_UINT32_MAX = 0xfffffff;
  36. // Values taken from:
  37. // http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
  38. const PR_RDONLY = 0x01;
  39. const PR_WRONLY = 0x02;
  40. const PR_RDWR = 0x04;
  41. const PR_CREATE_FILE = 0x08;
  42. const PR_APPEND = 0x10;
  43. const PR_TRUNCATE = 0x20;
  44. const PR_SYNC = 0x40;
  45. const PR_EXCL = 0x80;
  46. const FLAGS = {
  47. "r": PR_RDONLY,
  48. "r+": PR_RDWR,
  49. "w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
  50. "w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
  51. "a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
  52. "a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR
  53. };
  54. function accessor() {
  55. let map = new WeakMap();
  56. return function(fd, value) {
  57. if (value === null) map.delete(fd);
  58. if (value !== undefined) map.set(fd, value);
  59. return map.get(fd);
  60. }
  61. }
  62. let nsIFile = accessor();
  63. let nsIFileInputStream = accessor();
  64. let nsIFileOutputStream = accessor();
  65. let nsIBinaryInputStream = accessor();
  66. let nsIBinaryOutputStream = accessor();
  67. // Just a contstant object used to signal that all of the file
  68. // needs to be read.
  69. const ALL = new String("Read all of the file");
  70. function isWritable(mode) !!(mode & PR_WRONLY || mode & PR_RDWR)
  71. function isReadable(mode) !!(mode & PR_RDONLY || mode & PR_RDWR)
  72. function isString(value) typeof(value) === "string"
  73. function isFunction(value) typeof(value) === "function"
  74. function toArray(enumerator) {
  75. let value = [];
  76. while(enumerator.hasMoreElements())
  77. value.push(enumerator.getNext())
  78. return value
  79. }
  80. function getFileName(file) file.QueryInterface(Ci.nsIFile).leafName
  81. function remove(path, recursive) {
  82. let fd = new nsILocalFile(path)
  83. if (fd.exists()) {
  84. fd.remove(recursive || false);
  85. }
  86. else {
  87. throw FSError("remove", "ENOENT", 34, path);
  88. }
  89. }
  90. /**
  91. * Utility function to convert either an octal number or string
  92. * into an octal number
  93. * 0777 => 0777
  94. * "0644" => 0644
  95. */
  96. function Mode(mode, fallback) {
  97. return isString(mode) ? parseInt(mode, 8) : mode || fallback;
  98. }
  99. function Flags(flag) {
  100. return !isString(flag) ? flag :
  101. FLAGS[flag] || Error("Unknown file open flag: " + flag);
  102. }
  103. function FSError(op, code, errno, path, file, line) {
  104. let error = Error(code + ", " + op + " " + path, file, line);
  105. error.code = code;
  106. error.path = path;
  107. error.errno = errno;
  108. return error;
  109. }
  110. const ReadStream = Class({
  111. extends: InputStream,
  112. initialize: function initialize(path, options) {
  113. this.position = -1;
  114. this.length = -1;
  115. this.flags = "r";
  116. this.mode = FILE_PERMISSION;
  117. this.bufferSize = 64 * 1024;
  118. options = options || {};
  119. if ("flags" in options && options.flags)
  120. this.flags = options.flags;
  121. if ("bufferSize" in options && options.bufferSize)
  122. this.bufferSize = options.bufferSize;
  123. if ("length" in options && options.length)
  124. this.length = options.length;
  125. if ("position" in options && options.position !== undefined)
  126. this.position = options.position;
  127. let { flags, mode, position, length } = this;
  128. let fd = isString(path) ? openSync(path, flags, mode) : path;
  129. this.fd = fd;
  130. let input = nsIFileInputStream(fd);
  131. // Setting a stream position, unless it"s `-1` which means current position.
  132. if (position >= 0)
  133. input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
  134. // We use `nsIStreamTransportService` service to transform blocking
  135. // file input stream into a fully asynchronous stream that can be written
  136. // without blocking the main thread.
  137. let transport = createInputTransport(input, position, length, false);
  138. // Open an input stream on a transport. We don"t pass flags to guarantee
  139. // non-blocking stream semantics. Also we use defaults for segment size &
  140. // count.
  141. InputStream.prototype.initialize.call(this, {
  142. asyncInputStream: transport.openInputStream(null, 0, 0)
  143. });
  144. // Close file descriptor on end and destroy the stream.
  145. on(this, "end", _ => {
  146. this.destroy();
  147. emit(this, "close");
  148. });
  149. this.read();
  150. },
  151. destroy: function() {
  152. closeSync(this.fd);
  153. InputStream.prototype.destroy.call(this);
  154. }
  155. });
  156. exports.ReadStream = ReadStream;
  157. exports.createReadStream = function createReadStream(path, options) {
  158. return new ReadStream(path, options);
  159. };
  160. const WriteStream = Class({
  161. extends: OutputStream,
  162. initialize: function initialize(path, options) {
  163. this.drainable = true;
  164. this.flags = "w";
  165. this.position = -1;
  166. this.mode = FILE_PERMISSION;
  167. options = options || {};
  168. if ("flags" in options && options.flags)
  169. this.flags = options.flags;
  170. if ("mode" in options && options.mode)
  171. this.mode = options.mode;
  172. if ("position" in options && options.position !== undefined)
  173. this.position = options.position;
  174. let { position, flags, mode } = this;
  175. // If pass was passed we create a file descriptor out of it. Otherwise
  176. // we just use given file descriptor.
  177. let fd = isString(path) ? openSync(path, flags, mode) : path;
  178. this.fd = fd;
  179. let output = nsIFileOutputStream(fd);
  180. // Setting a stream position, unless it"s `-1` which means current position.
  181. if (position >= 0)
  182. output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
  183. // We use `nsIStreamTransportService` service to transform blocking
  184. // file output stream into a fully asynchronous stream that can be written
  185. // without blocking the main thread.
  186. let transport = createOutputTransport(output, position, -1, false);
  187. // Open an output stream on a transport. We don"t pass flags to guarantee
  188. // non-blocking stream semantics. Also we use defaults for segment size &
  189. // count.
  190. OutputStream.prototype.initialize.call(this, {
  191. asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0),
  192. output: output
  193. });
  194. // For write streams "finish" basically means close.
  195. on(this, "finish", _ => {
  196. this.destroy();
  197. emit(this, "close");
  198. });
  199. },
  200. destroy: function() {
  201. OutputStream.prototype.destroy.call(this);
  202. closeSync(this.fd);
  203. }
  204. });
  205. exports.WriteStream = WriteStream;
  206. exports.createWriteStream = function createWriteStream(path, options) {
  207. return new WriteStream(path, options);
  208. };
  209. const Stats = Class({
  210. initialize: function initialize(path) {
  211. let file = new nsILocalFile(path);
  212. if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
  213. nsIFile(this, file);
  214. },
  215. isDirectory: function() nsIFile(this).isDirectory(),
  216. isFile: function() nsIFile(this).isFile(),
  217. isSymbolicLink: function() nsIFile(this).isSymlink(),
  218. get mode() nsIFile(this).permissions,
  219. get size() nsIFile(this).fileSize,
  220. get mtime() nsIFile(this).lastModifiedTime,
  221. isBlockDevice: function() nsIFile(this).isSpecial(),
  222. isCharacterDevice: function() nsIFile(this).isSpecial(),
  223. isFIFO: function() nsIFile(this).isSpecial(),
  224. isSocket: function() nsIFile(this).isSpecial(),
  225. // non standard
  226. get exists() nsIFile(this).exists(),
  227. get hidden() nsIFile(this).isHidden(),
  228. get writable() nsIFile(this).isWritable(),
  229. get readable() nsIFile(this).isReadable()
  230. });
  231. exports.Stats = Stats;
  232. const LStats = Class({
  233. extends: Stats,
  234. get size() this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
  235. nsIFile(this).fileSize,
  236. get mtime() this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
  237. nsIFile(this).lastModifiedTime,
  238. // non standard
  239. get permissions() this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
  240. nsIFile(this).permissions
  241. });
  242. const FStat = Class({
  243. extends: Stats,
  244. initialize: function initialize(fd) {
  245. nsIFile(this, nsIFile(fd));
  246. }
  247. });
  248. function noop() {}
  249. function Async(wrapped) {
  250. return function (path, callback) {
  251. let args = Array.slice(arguments);
  252. callback = args.pop();
  253. // If node is not given a callback argument
  254. // it just does not calls it.
  255. if (typeof(callback) !== "function") {
  256. args.push(callback);
  257. callback = noop;
  258. }
  259. setTimeout(function() {
  260. try {
  261. var result = wrapped.apply(this, args);
  262. if (result === undefined) callback(null);
  263. else callback(null, result);
  264. } catch (error) {
  265. callback(error);
  266. }
  267. }, 0);
  268. }
  269. }
  270. /**
  271. * Synchronous rename(2)
  272. */
  273. function renameSync(oldPath, newPath) {
  274. let source = new nsILocalFile(oldPath);
  275. let target = new nsILocalFile(newPath);
  276. if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
  277. return source.moveTo(target.parent, target.leafName);
  278. };
  279. exports.renameSync = renameSync;
  280. /**
  281. * Asynchronous rename(2). No arguments other than a possible exception are
  282. * given to the completion callback.
  283. */
  284. let rename = Async(renameSync);
  285. exports.rename = rename;
  286. /**
  287. * Test whether or not the given path exists by checking with the file system.
  288. */
  289. function existsSync(path) {
  290. return new nsILocalFile(path).exists();
  291. }
  292. exports.existsSync = existsSync;
  293. let exists = Async(existsSync);
  294. exports.exists = exists;
  295. /**
  296. * Synchronous ftruncate(2).
  297. */
  298. function truncateSync(path, length) {
  299. let fd = openSync(path, "w");
  300. ftruncateSync(fd, length);
  301. closeSync(fd);
  302. }
  303. exports.truncateSync = truncateSync;
  304. /**
  305. * Asynchronous ftruncate(2). No arguments other than a possible exception are
  306. * given to the completion callback.
  307. */
  308. function truncate(path, length, callback) {
  309. open(path, "w", function(error, fd) {
  310. if (error) return callback(error);
  311. ftruncate(fd, length, function(error) {
  312. if (error) {
  313. closeSync(fd);
  314. callback(error);
  315. }
  316. else {
  317. close(fd, callback);
  318. }
  319. });
  320. });
  321. }
  322. exports.truncate = truncate;
  323. function ftruncate(fd, length, callback) {
  324. write(fd, new Buffer(length), 0, length, 0, function(error) {
  325. callback(error);
  326. });
  327. }
  328. exports.ftruncate = ftruncate;
  329. function ftruncateSync(fd, length = 0) {
  330. writeSync(fd, new Buffer(length), 0, length, 0);
  331. }
  332. exports.ftruncateSync = ftruncateSync;
  333. function chownSync(path, uid, gid) {
  334. throw Error("Not implemented yet!!");
  335. }
  336. exports.chownSync = chownSync;
  337. let chown = Async(chownSync);
  338. exports.chown = chown;
  339. function lchownSync(path, uid, gid) {
  340. throw Error("Not implemented yet!!");
  341. }
  342. exports.lchownSync = chownSync;
  343. let lchown = Async(lchown);
  344. exports.lchown = lchown;
  345. /**
  346. * Synchronous chmod(2).
  347. */
  348. function chmodSync (path, mode) {
  349. let file;
  350. try {
  351. file = new nsILocalFile(path);
  352. } catch(e) {
  353. throw FSError("chmod", "ENOENT", 34, path);
  354. }
  355. file.permissions = Mode(mode);
  356. }
  357. exports.chmodSync = chmodSync;
  358. /**
  359. * Asynchronous chmod(2). No arguments other than a possible exception are
  360. * given to the completion callback.
  361. */
  362. let chmod = Async(chmodSync);
  363. exports.chmod = chmod;
  364. /**
  365. * Synchronous chmod(2).
  366. */
  367. function fchmodSync(fd, mode) {
  368. throw Error("Not implemented yet!!");
  369. };
  370. exports.fchmodSync = fchmodSync;
  371. /**
  372. * Asynchronous chmod(2). No arguments other than a possible exception are
  373. * given to the completion callback.
  374. */
  375. let fchmod = Async(fchmodSync);
  376. exports.fchmod = fchmod;
  377. /**
  378. * Synchronous stat(2). Returns an instance of `fs.Stats`
  379. */
  380. function statSync(path) {
  381. return new Stats(path);
  382. };
  383. exports.statSync = statSync;
  384. /**
  385. * Asynchronous stat(2). The callback gets two arguments (err, stats) where
  386. * stats is a `fs.Stats` object. It looks like this:
  387. */
  388. let stat = Async(statSync);
  389. exports.stat = stat;
  390. /**
  391. * Synchronous lstat(2). Returns an instance of `fs.Stats`.
  392. */
  393. function lstatSync(path) {
  394. return new LStats(path);
  395. };
  396. exports.lstatSync = lstatSync;
  397. /**
  398. * Asynchronous lstat(2). The callback gets two arguments (err, stats) where
  399. * stats is a fs.Stats object. lstat() is identical to stat(), except that if
  400. * path is a symbolic link, then the link itself is stat-ed, not the file that
  401. * it refers to.
  402. */
  403. let lstat = Async(lstatSync);
  404. exports.lstat = lstat;
  405. /**
  406. * Synchronous fstat(2). Returns an instance of `fs.Stats`.
  407. */
  408. function fstatSync(fd) {
  409. return new FStat(fd);
  410. };
  411. exports.fstatSync = fstatSync;
  412. /**
  413. * Asynchronous fstat(2). The callback gets two arguments (err, stats) where
  414. * stats is a fs.Stats object.
  415. */
  416. let fstat = Async(fstatSync);
  417. exports.fstat = fstat;
  418. /**
  419. * Synchronous link(2).
  420. */
  421. function linkSync(source, target) {
  422. throw Error("Not implemented yet!!");
  423. };
  424. exports.linkSync = linkSync;
  425. /**
  426. * Asynchronous link(2). No arguments other than a possible exception are given
  427. * to the completion callback.
  428. */
  429. let link = Async(linkSync);
  430. exports.link = link;
  431. /**
  432. * Synchronous symlink(2).
  433. */
  434. function symlinkSync(source, target) {
  435. throw Error("Not implemented yet!!");
  436. };
  437. exports.symlinkSync = symlinkSync;
  438. /**
  439. * Asynchronous symlink(2). No arguments other than a possible exception are
  440. * given to the completion callback.
  441. */
  442. let symlink = Async(symlinkSync);
  443. exports.symlink = symlink;
  444. /**
  445. * Synchronous readlink(2). Returns the resolved path.
  446. */
  447. function readlinkSync(path) {
  448. return new nsILocalFile(path).target;
  449. };
  450. exports.readlinkSync = readlinkSync;
  451. /**
  452. * Asynchronous readlink(2). The callback gets two arguments
  453. * `(error, resolvedPath)`.
  454. */
  455. let readlink = Async(readlinkSync);
  456. exports.readlink = readlink;
  457. /**
  458. * Synchronous realpath(2). Returns the resolved path.
  459. */
  460. function realpathSync(path) {
  461. return new nsILocalFile(path).path;
  462. };
  463. exports.realpathSync = realpathSync;
  464. /**
  465. * Asynchronous realpath(2). The callback gets two arguments
  466. * `(err, resolvedPath)`.
  467. */
  468. let realpath = Async(realpathSync);
  469. exports.realpath = realpath;
  470. /**
  471. * Synchronous unlink(2).
  472. */
  473. let unlinkSync = remove;
  474. exports.unlinkSync = unlinkSync;
  475. /**
  476. * Asynchronous unlink(2). No arguments other than a possible exception are
  477. * given to the completion callback.
  478. */
  479. let unlink = Async(remove);
  480. exports.unlink = unlink;
  481. /**
  482. * Synchronous rmdir(2).
  483. */
  484. let rmdirSync = remove;
  485. exports.rmdirSync = rmdirSync;
  486. /**
  487. * Asynchronous rmdir(2). No arguments other than a possible exception are
  488. * given to the completion callback.
  489. */
  490. let rmdir = Async(rmdirSync);
  491. exports.rmdir = rmdir;
  492. /**
  493. * Synchronous mkdir(2).
  494. */
  495. function mkdirSync(path, mode) {
  496. try {
  497. return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
  498. } catch (error) {
  499. // Adjust exception thorw to match ones thrown by node.
  500. if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
  501. let { fileName, lineNumber } = error;
  502. error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
  503. }
  504. throw error;
  505. }
  506. };
  507. exports.mkdirSync = mkdirSync;
  508. /**
  509. * Asynchronous mkdir(2). No arguments other than a possible exception are
  510. * given to the completion callback.
  511. */
  512. let mkdir = Async(mkdirSync);
  513. exports.mkdir = mkdir;
  514. /**
  515. * Synchronous readdir(3). Returns an array of filenames excluding `"."` and
  516. * `".."`.
  517. */
  518. function readdirSync(path) {
  519. try {
  520. return toArray(new nsILocalFile(path).directoryEntries).map(getFileName);
  521. }
  522. catch (error) {
  523. // Adjust exception thorw to match ones thrown by node.
  524. if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
  525. error.name === "NS_ERROR_FILE_NOT_FOUND")
  526. {
  527. let { fileName, lineNumber } = error;
  528. error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
  529. }
  530. throw error;
  531. }
  532. };
  533. exports.readdirSync = readdirSync;
  534. /**
  535. * Asynchronous readdir(3). Reads the contents of a directory. The callback
  536. * gets two arguments `(error, files)` where `files` is an array of the names
  537. * of the files in the directory excluding `"."` and `".."`.
  538. */
  539. let readdir = Async(readdirSync);
  540. exports.readdir = readdir;
  541. /**
  542. * Synchronous close(2).
  543. */
  544. function closeSync(fd) {
  545. let input = nsIFileInputStream(fd);
  546. let output = nsIFileOutputStream(fd);
  547. // Closing input stream and removing reference.
  548. if (input) input.close();
  549. // Closing output stream and removing reference.
  550. if (output) output.close();
  551. nsIFile(fd, null);
  552. nsIFileInputStream(fd, null);
  553. nsIFileOutputStream(fd, null);
  554. nsIBinaryInputStream(fd, null);
  555. nsIBinaryOutputStream(fd, null);
  556. };
  557. exports.closeSync = closeSync;
  558. /**
  559. * Asynchronous close(2). No arguments other than a possible exception are
  560. * given to the completion callback.
  561. */
  562. let close = Async(closeSync);
  563. exports.close = close;
  564. /**
  565. * Synchronous open(2).
  566. */
  567. function openSync(path, flags, mode) {
  568. let [ fd, flags, mode, file ] =
  569. [ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ];
  570. nsIFile(fd, file);
  571. // If trying to open file for just read that does not exists
  572. // need to throw exception as node does.
  573. if (!file.exists() && !isWritable(flags))
  574. throw FSError("open", "ENOENT", 34, path);
  575. // If we want to open file in read mode we initialize input stream.
  576. if (isReadable(flags)) {
  577. let input = FileInputStream(file, flags, mode, DEFER_OPEN);
  578. nsIFileInputStream(fd, input);
  579. }
  580. // If we want to open file in write mode we initialize output stream for it.
  581. if (isWritable(flags)) {
  582. let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
  583. nsIFileOutputStream(fd, output);
  584. }
  585. return fd;
  586. }
  587. exports.openSync = openSync;
  588. /**
  589. * Asynchronous file open. See open(2). Flags can be
  590. * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
  591. * The callback gets two arguments `(error, fd).
  592. */
  593. let open = Async(openSync);
  594. exports.open = open;
  595. /**
  596. * Synchronous version of buffer-based fs.write(). Returns the number of bytes
  597. * written.
  598. */
  599. function writeSync(fd, buffer, offset, length, position) {
  600. if (length + offset > buffer.length) {
  601. throw Error("Length is extends beyond buffer");
  602. }
  603. else if (length + offset !== buffer.length) {
  604. buffer = buffer.slice(offset, offset + length);
  605. }
  606. let writeStream = new WriteStream(fd, { position: position,
  607. length: length });
  608. let output = BinaryOutputStream(nsIFileOutputStream(fd));
  609. nsIBinaryOutputStream(fd, output);
  610. // We write content as a byte array as this will avoid any transcoding
  611. // if content was a buffer.
  612. output.writeByteArray(buffer.valueOf(), buffer.length);
  613. output.flush();
  614. };
  615. exports.writeSync = writeSync;
  616. /**
  617. * Write buffer to the file specified by fd.
  618. *
  619. * `offset` and `length` determine the part of the buffer to be written.
  620. *
  621. * `position` refers to the offset from the beginning of the file where this
  622. * data should be written. If `position` is `null`, the data will be written
  623. * at the current position. See pwrite(2).
  624. *
  625. * The callback will be given three arguments `(error, written, buffer)` where
  626. * written specifies how many bytes were written into buffer.
  627. *
  628. * Note that it is unsafe to use `fs.write` multiple times on the same file
  629. * without waiting for the callback.
  630. */
  631. function write(fd, buffer, offset, length, position, callback) {
  632. if (!Buffer.isBuffer(buffer)) {
  633. // (fd, data, position, encoding, callback)
  634. let encoding = null;
  635. [ position, encoding, callback ] = Array.slice(arguments, 1);
  636. buffer = new Buffer(String(buffer), encoding);
  637. offset = 0;
  638. } else if (length + offset > buffer.length) {
  639. throw Error("Length is extends beyond buffer");
  640. } else if (length + offset !== buffer.length) {
  641. buffer = buffer.slice(offset, offset + length);
  642. }
  643. let writeStream = new WriteStream(fd, { position: position,
  644. length: length });
  645. writeStream.on("error", callback);
  646. writeStream.write(buffer, function onEnd() {
  647. writeStream.destroy();
  648. if (callback)
  649. callback(null, buffer.length, buffer);
  650. });
  651. };
  652. exports.write = write;
  653. /**
  654. * Synchronous version of string-based fs.read. Returns the number of
  655. * bytes read.
  656. */
  657. function readSync(fd, buffer, offset, length, position) {
  658. let input = nsIFileInputStream(fd);
  659. // Setting a stream position, unless it"s `-1` which means current position.
  660. if (position >= 0)
  661. input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
  662. // We use `nsIStreamTransportService` service to transform blocking
  663. // file input stream into a fully asynchronous stream that can be written
  664. // without blocking the main thread.
  665. let binaryInputStream = BinaryInputStream(input);
  666. let count = length === ALL ? binaryInputStream.available() : length;
  667. if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer);
  668. else {
  669. let chunk = new Buffer(count);
  670. binaryInputStream.readArrayBuffer(count, chunk.buffer);
  671. chunk.copy(buffer, offset);
  672. }
  673. return buffer.slice(offset, offset + count);
  674. };
  675. exports.readSync = readSync;
  676. /**
  677. * Read data from the file specified by `fd`.
  678. *
  679. * `buffer` is the buffer that the data will be written to.
  680. * `offset` is offset within the buffer where writing will start.
  681. *
  682. * `length` is an integer specifying the number of bytes to read.
  683. *
  684. * `position` is an integer specifying where to begin reading from in the file.
  685. * If `position` is `null`, data will be read from the current file position.
  686. *
  687. * The callback is given the three arguments, `(error, bytesRead, buffer)`.
  688. */
  689. function read(fd, buffer, offset, length, position, callback) {
  690. let bytesRead = 0;
  691. let readStream = new ReadStream(fd, { position: position, length: length });
  692. readStream.on("data", function onData(data) {
  693. data.copy(buffer, offset + bytesRead);
  694. bytesRead += data.length;
  695. });
  696. readStream.on("end", function onEnd() {
  697. callback(null, bytesRead, buffer);
  698. readStream.destroy();
  699. });
  700. };
  701. exports.read = read;
  702. /**
  703. * Asynchronously reads the entire contents of a file.
  704. * The callback is passed two arguments `(error, data)`, where data is the
  705. * contents of the file.
  706. */
  707. function readFile(path, encoding, callback) {
  708. if (isFunction(encoding)) {
  709. callback = encoding
  710. encoding = null
  711. }
  712. let buffer = null;
  713. try {
  714. let readStream = new ReadStream(path);
  715. readStream.on("data", function(data) {
  716. if (!buffer) buffer = data;
  717. else buffer = Buffer.concat([buffer, data], 2);
  718. });
  719. readStream.on("error", function onError(error) {
  720. callback(error);
  721. });
  722. readStream.on("end", function onEnd() {
  723. // Note: Need to destroy before invoking a callback
  724. // so that file descriptor is released.
  725. readStream.destroy();
  726. callback(null, buffer);
  727. });
  728. } catch (error) {
  729. setTimeout(callback, 0, error);
  730. }
  731. };
  732. exports.readFile = readFile;
  733. /**
  734. * Synchronous version of `fs.readFile`. Returns the contents of the path.
  735. * If encoding is specified then this function returns a string.
  736. * Otherwise it returns a buffer.
  737. */
  738. function readFileSync(path, encoding) {
  739. let fd = openSync(path, "r");
  740. let size = fstatSync(fd).size;
  741. let buffer = new Buffer(size);
  742. try {
  743. readSync(fd, buffer, 0, ALL, 0);
  744. }
  745. finally {
  746. closeSync(fd);
  747. }
  748. return buffer;
  749. };
  750. exports.readFileSync = readFileSync;
  751. /**
  752. * Asynchronously writes data to a file, replacing the file if it already
  753. * exists. data can be a string or a buffer.
  754. */
  755. function writeFile(path, content, encoding, callback) {
  756. if (!isString(path))
  757. throw new TypeError('path must be a string');
  758. try {
  759. if (isFunction(encoding)) {
  760. callback = encoding
  761. encoding = null
  762. }
  763. if (isString(content))
  764. content = new Buffer(content, encoding);
  765. let writeStream = new WriteStream(path);
  766. let error = null;
  767. writeStream.end(content, function() {
  768. writeStream.destroy();
  769. callback(error);
  770. });
  771. writeStream.on("error", function onError(reason) {
  772. error = reason;
  773. writeStream.destroy();
  774. });
  775. } catch (error) {
  776. callback(error);
  777. }
  778. };
  779. exports.writeFile = writeFile;
  780. /**
  781. * The synchronous version of `fs.writeFile`.
  782. */
  783. function writeFileSync(filename, data, encoding) {
  784. throw Error("Not implemented");
  785. };
  786. exports.writeFileSync = writeFileSync;
  787. function utimesSync(path, atime, mtime) {
  788. throw Error("Not implemented");
  789. }
  790. exports.utimesSync = utimesSync;
  791. let utimes = Async(utimesSync);
  792. exports.utimes = utimes;
  793. function futimesSync(fd, atime, mtime, callback) {
  794. throw Error("Not implemented");
  795. }
  796. exports.futimesSync = futimesSync;
  797. let futimes = Async(futimesSync);
  798. exports.futimes = futimes;
  799. function fsyncSync(fd, atime, mtime, callback) {
  800. throw Error("Not implemented");
  801. }
  802. exports.fsyncSync = fsyncSync;
  803. let fsync = Async(fsyncSync);
  804. exports.fsync = fsync;
  805. /**
  806. * Watch for changes on filename. The callback listener will be called each
  807. * time the file is accessed.
  808. *
  809. * The second argument is optional. The options if provided should be an object
  810. * containing two members a boolean, persistent, and interval, a polling value
  811. * in milliseconds. The default is { persistent: true, interval: 0 }.
  812. */
  813. function watchFile(path, options, listener) {
  814. throw Error("Not implemented");
  815. };
  816. exports.watchFile = watchFile;
  817. function unwatchFile(path, listener) {
  818. throw Error("Not implemented");
  819. }
  820. exports.unwatchFile = unwatchFile;
  821. function watch(path, options, listener) {
  822. throw Error("Not implemented");
  823. }
  824. exports.watch = watch;