• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectDefineProperty,
5  ObjectSetPrototypeOf,
6  Symbol,
7} = primordials;
8
9const errors = require('internal/errors');
10const {
11  kFsStatsFieldsNumber,
12  StatWatcher: _StatWatcher
13} = internalBinding('fs');
14const { FSEvent } = internalBinding('fs_event_wrap');
15const { UV_ENOSPC } = internalBinding('uv');
16const { EventEmitter } = require('events');
17const {
18  getStatsFromBinding,
19  getValidatedPath
20} = require('internal/fs/utils');
21const {
22  defaultTriggerAsyncIdScope,
23  symbols: { owner_symbol }
24} = require('internal/async_hooks');
25const { toNamespacedPath } = require('path');
26const { validateUint32 } = require('internal/validators');
27const assert = require('internal/assert');
28
29const kOldStatus = Symbol('kOldStatus');
30const kUseBigint = Symbol('kUseBigint');
31
32const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
33const KFSStatWatcherMaxRefCount = Symbol('KFSStatWatcherMaxRefCount');
34const kFSStatWatcherAddOrCleanRef = Symbol('kFSStatWatcherAddOrCleanRef');
35
36function emitStop(self) {
37  self.emit('stop');
38}
39
40function StatWatcher(bigint) {
41  EventEmitter.call(this);
42
43  this._handle = null;
44  this[kOldStatus] = -1;
45  this[kUseBigint] = bigint;
46  this[KFSStatWatcherRefCount] = 1;
47  this[KFSStatWatcherMaxRefCount] = 1;
48}
49ObjectSetPrototypeOf(StatWatcher.prototype, EventEmitter.prototype);
50ObjectSetPrototypeOf(StatWatcher, EventEmitter);
51
52function onchange(newStatus, stats) {
53  const self = this[owner_symbol];
54  if (self[kOldStatus] === -1 &&
55      newStatus === -1 &&
56      stats[2/* new nlink */] === stats[16/* old nlink */]) {
57    return;
58  }
59
60  self[kOldStatus] = newStatus;
61  self.emit('change', getStatsFromBinding(stats),
62            getStatsFromBinding(stats, kFsStatsFieldsNumber));
63}
64
65// FIXME(joyeecheung): this method is not documented.
66// At the moment if filename is undefined, we
67// 1. Throw an Error if it's the first time .start() is called
68// 2. Return silently if .start() has already been called
69//    on a valid filename and the wrap has been initialized
70// This method is a noop if the watcher has already been started.
71StatWatcher.prototype.start = function(filename, persistent, interval) {
72  if (this._handle !== null)
73    return;
74
75  this._handle = new _StatWatcher(this[kUseBigint]);
76  this._handle[owner_symbol] = this;
77  this._handle.onchange = onchange;
78  if (!persistent)
79    this.unref();
80
81  // uv_fs_poll is a little more powerful than ev_stat but we curb it for
82  // the sake of backwards compatibility
83  this[kOldStatus] = -1;
84
85  filename = getValidatedPath(filename, 'filename');
86  validateUint32(interval, 'interval');
87  const err = this._handle.start(toNamespacedPath(filename), interval);
88  if (err) {
89    const error = errors.uvException({
90      errno: err,
91      syscall: 'watch',
92      path: filename
93    });
94    error.filename = filename;
95    throw error;
96  }
97};
98
99// FIXME(joyeecheung): this method is not documented while there is
100// another documented fs.unwatchFile(). The counterpart in
101// FSWatcher is .close()
102// This method is a noop if the watcher has not been started.
103StatWatcher.prototype.stop = function() {
104  if (this._handle === null)
105    return;
106
107  defaultTriggerAsyncIdScope(this._handle.getAsyncId(),
108                             process.nextTick,
109                             emitStop,
110                             this);
111  this._handle.close();
112  this._handle = null;
113};
114
115// Clean up or add ref counters.
116StatWatcher.prototype[kFSStatWatcherAddOrCleanRef] = function(operate) {
117  if (operate === 'add') {
118    // Add a Ref
119    this[KFSStatWatcherRefCount]++;
120    this[KFSStatWatcherMaxRefCount]++;
121  } else if (operate === 'clean') {
122    // Clean up a single
123    this[KFSStatWatcherMaxRefCount]--;
124    this.unref();
125  } else if (operate === 'cleanAll') {
126    // Clean up all
127    this[KFSStatWatcherMaxRefCount] = 0;
128    this[KFSStatWatcherRefCount] = 0;
129    this._handle && this._handle.unref();
130  }
131};
132
133StatWatcher.prototype.ref = function() {
134  // Avoid refCount calling ref multiple times causing unref to have no effect.
135  if (this[KFSStatWatcherRefCount] === this[KFSStatWatcherMaxRefCount])
136    return this;
137  if (this._handle && this[KFSStatWatcherRefCount]++ === 0)
138    this._handle.ref();
139  return this;
140};
141
142StatWatcher.prototype.unref = function() {
143  // Avoid refCount calling unref multiple times causing ref to have no effect.
144  if (this[KFSStatWatcherRefCount] === 0) return this;
145  if (this._handle && --this[KFSStatWatcherRefCount] === 0)
146    this._handle.unref();
147  return this;
148};
149
150
151function FSWatcher() {
152  EventEmitter.call(this);
153
154  this._handle = new FSEvent();
155  this._handle[owner_symbol] = this;
156
157  this._handle.onchange = (status, eventType, filename) => {
158    // TODO(joyeecheung): we may check self._handle.initialized here
159    // and return if that is false. This allows us to avoid firing the event
160    // after the handle is closed, and to fire both UV_RENAME and UV_CHANGE
161    // if they are set by libuv at the same time.
162    if (status < 0) {
163      if (this._handle !== null) {
164        // We don't use this.close() here to avoid firing the close event.
165        this._handle.close();
166        this._handle = null;  // Make the handle garbage collectable
167      }
168      const error = errors.uvException({
169        errno: status,
170        syscall: 'watch',
171        path: filename
172      });
173      error.filename = filename;
174      this.emit('error', error);
175    } else {
176      this.emit('change', eventType, filename);
177    }
178  };
179}
180ObjectSetPrototypeOf(FSWatcher.prototype, EventEmitter.prototype);
181ObjectSetPrototypeOf(FSWatcher, EventEmitter);
182
183
184// FIXME(joyeecheung): this method is not documented.
185// At the moment if filename is undefined, we
186// 1. Throw an Error if it's the first time .start() is called
187// 2. Return silently if .start() has already been called
188//    on a valid filename and the wrap has been initialized
189// 3. Return silently if the watcher has already been closed
190// This method is a noop if the watcher has already been started.
191FSWatcher.prototype.start = function(filename,
192                                     persistent,
193                                     recursive,
194                                     encoding) {
195  if (this._handle === null) {  // closed
196    return;
197  }
198  assert(this._handle instanceof FSEvent, 'handle must be a FSEvent');
199  if (this._handle.initialized) {  // already started
200    return;
201  }
202
203  filename = getValidatedPath(filename, 'filename');
204
205  const err = this._handle.start(toNamespacedPath(filename),
206                                 persistent,
207                                 recursive,
208                                 encoding);
209  if (err) {
210    const error = errors.uvException({
211      errno: err,
212      syscall: 'watch',
213      path: filename,
214      message: err === UV_ENOSPC ?
215        'System limit for number of file watchers reached' : ''
216    });
217    error.filename = filename;
218    throw error;
219  }
220};
221
222// This method is a noop if the watcher has not been started or
223// has already been closed.
224FSWatcher.prototype.close = function() {
225  if (this._handle === null) {  // closed
226    return;
227  }
228  assert(this._handle instanceof FSEvent, 'handle must be a FSEvent');
229  if (!this._handle.initialized) {  // not started
230    return;
231  }
232  this._handle.close();
233  this._handle = null;  // Make the handle garbage collectable
234  process.nextTick(emitCloseNT, this);
235};
236
237FSWatcher.prototype.ref = function() {
238  if (this._handle) this._handle.ref();
239  return this;
240};
241
242FSWatcher.prototype.unref = function() {
243  if (this._handle) this._handle.unref();
244  return this;
245};
246
247function emitCloseNT(self) {
248  self.emit('close');
249}
250
251// Legacy alias on the C++ wrapper object. This is not public API, so we may
252// want to runtime-deprecate it at some point. There's no hurry, though.
253ObjectDefineProperty(FSEvent.prototype, 'owner', {
254  get() { return this[owner_symbol]; },
255  set(v) { return this[owner_symbol] = v; }
256});
257
258module.exports = {
259  FSWatcher,
260  StatWatcher,
261  kFSStatWatcherAddOrCleanRef,
262};
263