fileOperations.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. 'use strict';
  2. var fs = require('graceful-fs');
  3. var assign = require('object-assign');
  4. var isEqual = require('lodash.isequal');
  5. var isValidDate = require('vali-date');
  6. // TODO shared module
  7. // TODO include sticky/setuid/setgid, i.e. 7777?
  8. var MASK_MODE = parseInt('0777', 8);
  9. var DEFAULT_FILE_MODE = parseInt('0666', 8);
  10. var APPEND_MODE_REGEXP = /a/;
  11. function closeFd(propagatedErr, fd, callback) {
  12. if (typeof fd !== 'number') {
  13. return callback(propagatedErr);
  14. }
  15. fs.close(fd, onClosed);
  16. function onClosed(closeErr) {
  17. if (propagatedErr || closeErr) {
  18. return callback(propagatedErr || closeErr);
  19. }
  20. callback();
  21. }
  22. }
  23. function getModeDiff(fsMode, vinylMode) {
  24. var modeDiff = 0;
  25. if (typeof vinylMode === 'number') {
  26. modeDiff = (vinylMode ^ fsMode) & MASK_MODE;
  27. }
  28. return modeDiff;
  29. }
  30. function getTimesDiff(fsStat, vinylStat) {
  31. if (!isValidDate(vinylStat.mtime)) {
  32. return;
  33. }
  34. if (isEqual(vinylStat.mtime, fsStat.mtime) &&
  35. isEqual(vinylStat.atime, fsStat.atime)) {
  36. return;
  37. }
  38. var atime;
  39. if (isValidDate(vinylStat.atime)) {
  40. atime = vinylStat.atime;
  41. } else {
  42. atime = fsStat.atime;
  43. }
  44. if (!isValidDate(atime)) {
  45. atime = undefined;
  46. }
  47. var timesDiff = {
  48. mtime: vinylStat.mtime,
  49. atime: atime,
  50. };
  51. return timesDiff;
  52. }
  53. function isOwner(fsStat) {
  54. var hasGetuid = (typeof process.getuid === 'function');
  55. var hasGeteuid = (typeof process.geteuid === 'function');
  56. // If we don't have either, assume we don't have permissions.
  57. // This should only happen on Windows.
  58. // Windows basically noops fchmod and errors on futimes called on directories.
  59. if (!hasGeteuid && !hasGetuid) {
  60. return false;
  61. }
  62. var uid;
  63. if (hasGeteuid) {
  64. uid = process.geteuid();
  65. } else {
  66. uid = process.getuid();
  67. }
  68. if (fsStat.uid !== uid && uid !== 0) {
  69. return false;
  70. }
  71. return true;
  72. }
  73. function updateMetadata(fd, file, callback) {
  74. fs.fstat(fd, onStat);
  75. function onStat(err, stat) {
  76. if (err) {
  77. return callback(err, fd);
  78. }
  79. // Check if mode needs to be updated
  80. var modeDiff = getModeDiff(stat.mode, file.stat.mode);
  81. // Check if atime/mtime need to be updated
  82. var timesDiff = getTimesDiff(stat, file.stat);
  83. // Set file.stat to the reflect current state on disk
  84. assign(file.stat, stat);
  85. // Nothing to do
  86. if (!modeDiff && !timesDiff) {
  87. return callback(null, fd);
  88. }
  89. // Check access, `futimes` and `fchmod` only work if we own the file,
  90. // or if we are effectively root.
  91. if (!isOwner(stat)) {
  92. return callback(null, fd);
  93. }
  94. if (modeDiff) {
  95. return mode();
  96. }
  97. times();
  98. function mode() {
  99. var mode = stat.mode ^ modeDiff;
  100. fs.fchmod(fd, mode, onFchmod);
  101. function onFchmod(fchmodErr) {
  102. if (!fchmodErr) {
  103. file.stat.mode = mode;
  104. }
  105. if (timesDiff) {
  106. return times(fchmodErr);
  107. }
  108. callback(fchmodErr, fd);
  109. }
  110. }
  111. function times(fchmodErr) {
  112. fs.futimes(fd, timesDiff.atime, timesDiff.mtime, onFutimes);
  113. function onFutimes(futimesErr) {
  114. if (!futimesErr) {
  115. file.stat.atime = timesDiff.atime;
  116. file.stat.mtime = timesDiff.mtime;
  117. }
  118. callback(fchmodErr || futimesErr, fd);
  119. }
  120. }
  121. }
  122. }
  123. /*
  124. Custom writeFile implementation because we need access to the
  125. file descriptor after the write is complete.
  126. Most of the implementation taken from node core.
  127. */
  128. function writeFile(path, data, options, callback) {
  129. if (typeof options === 'function') {
  130. callback = options;
  131. options = {};
  132. }
  133. if (!Buffer.isBuffer(data)) {
  134. callback(new TypeError('Data must be a Buffer'));
  135. return;
  136. }
  137. if (!options) {
  138. options = {};
  139. }
  140. // Default the same as node
  141. var mode = options.mode || DEFAULT_FILE_MODE;
  142. var flag = options.flag || 'w';
  143. var position = APPEND_MODE_REGEXP.test(flag) ? null : 0;
  144. fs.open(path, flag, mode, onOpen);
  145. function onOpen(err, fd) {
  146. if (err) {
  147. return onComplete(err);
  148. }
  149. fs.write(fd, data, 0, data.length, position, onComplete);
  150. function onComplete(err) {
  151. callback(err, fd);
  152. }
  153. }
  154. }
  155. module.exports = {
  156. closeFd: closeFd,
  157. getModeDiff: getModeDiff,
  158. getTimesDiff: getTimesDiff,
  159. isOwner: isOwner,
  160. updateMetadata: updateMetadata,
  161. writeFile: writeFile,
  162. };