1 | var path = require('path');
|
2 |
|
3 | var File = require('./file').File;
|
4 | var FileDescriptor = require('./descriptor').FileDescriptor;
|
5 | var Directory = require('./directory').Directory;
|
6 |
|
7 | var constants = process.binding('constants');
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | function maybeCallback(callback, thisArg, func) {
|
19 | if (callback) {
|
20 | process.nextTick(function() {
|
21 | var err = null;
|
22 | var val;
|
23 | try {
|
24 | val = func.call(thisArg);
|
25 | } catch (e) {
|
26 | err = e;
|
27 | }
|
28 | callback(err, val);
|
29 | });
|
30 | } else {
|
31 | return func.call(thisArg);
|
32 | }
|
33 | }
|
34 |
|
35 | function notImplemented() {
|
36 | throw new Error('Method not implemented');
|
37 | }
|
38 |
|
39 | function noSuchFile(filepath, source) {
|
40 | var code = 'ENOENT';
|
41 | var errno = 34;
|
42 | var error = new Error(code + ', ' + source + ' \'' + filepath + '\'');
|
43 | error.code = code;
|
44 | error.errno = errno;
|
45 | return error;
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function Stats(config) {
|
56 | for (var key in config) {
|
57 | this[key] = config[key];
|
58 | }
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | Stats.prototype._checkModeProperty = function(property) {
|
68 | return ((this.mode & constants.S_IFMT) === property);
|
69 | };
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | Stats.prototype.isDirectory = function() {
|
76 | return this._checkModeProperty(constants.S_IFDIR);
|
77 | };
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | Stats.prototype.isFile = function() {
|
84 | return this._checkModeProperty(constants.S_IFREG);
|
85 | };
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | Stats.prototype.isBlockDevice = function() {
|
92 | return this._checkModeProperty(constants.S_IFBLK);
|
93 | };
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | Stats.prototype.isCharacterDevice = function() {
|
100 | return this._checkModeProperty(constants.S_IFCHR);
|
101 | };
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | Stats.prototype.isSymbolicLink = function() {
|
108 | return this._checkModeProperty(constants.S_IFLNK);
|
109 | };
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | Stats.prototype.isFIFO = function() {
|
116 | return this._checkModeProperty(constants.S_IFIFO);
|
117 | };
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | Stats.prototype.isSocket = function() {
|
124 | return this._checkModeProperty(constants.S_IFSOCK);
|
125 | };
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | var Binding = exports.Binding = function(system) {
|
135 |
|
136 | |
137 |
|
138 |
|
139 |
|
140 | this._system = system;
|
141 |
|
142 | |
143 |
|
144 |
|
145 |
|
146 | this.Stats = Stats;
|
147 |
|
148 | |
149 |
|
150 |
|
151 |
|
152 | this._openFiles = {};
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 | this._counter = 0;
|
159 |
|
160 | };
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | Binding.prototype.getSystem = function() {
|
168 | return this._system;
|
169 | };
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | Binding.prototype.setSystem = function(system) {
|
177 | this._system = system;
|
178 | };
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | Binding.prototype._getDescriptorById = function(fd) {
|
187 | if (!this._openFiles.hasOwnProperty(fd)) {
|
188 | throw new Error('EBADF, bad file descriptor');
|
189 | }
|
190 | return this._openFiles[fd];
|
191 | };
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | Binding.prototype._trackDescriptor = function(descriptor) {
|
200 | var fd = ++this._counter;
|
201 | this._openFiles[fd] = descriptor;
|
202 | return fd;
|
203 | };
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | Binding.prototype._untrackDescriptorById = function(fd) {
|
211 | if (!this._openFiles.hasOwnProperty(fd)) {
|
212 | throw new Error('EBADF, bad file descriptor');
|
213 | }
|
214 | delete this._openFiles[fd];
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | Binding.prototype.stat = function(filepath, callback) {
|
225 | return maybeCallback(callback, this, function() {
|
226 | var item = this._system.getItem(filepath);
|
227 | if (!item) {
|
228 | throw noSuchFile(filepath, 'stat');
|
229 | }
|
230 | return new Stats(item.getStats());
|
231 | });
|
232 | };
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | Binding.prototype.fstat = function(fd, callback) {
|
242 | return maybeCallback(callback, this, function() {
|
243 | var descriptor = this._getDescriptorById(fd);
|
244 | var item = descriptor.getItem();
|
245 | return new Stats(item.getStats());
|
246 | });
|
247 | };
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | Binding.prototype.close = function(fd, callback) {
|
256 | maybeCallback(callback, this, function() {
|
257 | this._untrackDescriptorById(fd);
|
258 | });
|
259 | };
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | Binding.prototype.open = function(pathname, flags, mode, callback) {
|
271 | return maybeCallback(callback, this, function() {
|
272 | var descriptor = new FileDescriptor(flags);
|
273 | var item = this._system.getItem(pathname);
|
274 | if (descriptor.isExclusive() && item) {
|
275 | throw new Error('EEXIST, file already exists');
|
276 | }
|
277 | if (descriptor.isCreate() && !item) {
|
278 | var parent = this._system.getItem(path.dirname(pathname));
|
279 | if (!parent) {
|
280 | throw new Error('ENOENT, no such file or directory');
|
281 | }
|
282 | if (!(parent instanceof Directory)) {
|
283 | throw new Error('ENOTDIR, not a directory');
|
284 | }
|
285 | item = new File(path.basename(pathname));
|
286 | item.setMode(mode);
|
287 | parent.addItem(item);
|
288 | }
|
289 | if (descriptor.isRead() && !item) {
|
290 | throw new Error('ENOENT, no such file or directory');
|
291 | }
|
292 | if (descriptor.isTruncate()) {
|
293 | item.setContent('');
|
294 | }
|
295 | if (descriptor.isTruncate() || descriptor.isAppend()) {
|
296 | descriptor.setPosition(item.getContent().length);
|
297 | }
|
298 | descriptor.setItem(item);
|
299 | return this._trackDescriptor(descriptor);
|
300 | });
|
301 | };
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 | Binding.prototype.read = function(fd, buffer, offset, length, position,
|
317 | callback) {
|
318 | return maybeCallback(callback, this, function() {
|
319 | var descriptor = this._getDescriptorById(fd);
|
320 | if (!descriptor.isRead()) {
|
321 | throw new Error('EBADF, bad file descriptor');
|
322 | }
|
323 | var file = descriptor.getItem();
|
324 | if (!(file instanceof File)) {
|
325 |
|
326 | throw new Error('EBADF, bad file descriptor');
|
327 | }
|
328 | if (typeof position !== 'number' || position < 0) {
|
329 | position = descriptor.getPosition();
|
330 | }
|
331 | var content = file.getContent();
|
332 | var start = Math.min(position, content.length - 1);
|
333 | var end = Math.min(position + length, content.length);
|
334 | var read = content.copy(buffer, offset, start, end);
|
335 | descriptor.setPosition(descriptor.getPosition() + read);
|
336 | return read;
|
337 | });
|
338 | };
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | Binding.prototype.write = function(fd, buffer, offset, length, position,
|
354 | callback) {
|
355 | return maybeCallback(callback, this, function() {
|
356 | var descriptor = this._getDescriptorById(fd);
|
357 | if (!descriptor.isWrite()) {
|
358 | throw new Error('EBADF, bad file descriptor');
|
359 | }
|
360 | var file = descriptor.getItem();
|
361 | if (!(file instanceof File)) {
|
362 |
|
363 | throw new Error('EBADF, bad file descriptor');
|
364 | }
|
365 | if (typeof position !== 'number' || position < 0) {
|
366 | position = descriptor.getPosition();
|
367 | }
|
368 | var content = file.getContent();
|
369 | var newLength = position + length;
|
370 | if (content.length < newLength) {
|
371 | var newContent = new Buffer(newLength);
|
372 | content.copy(newContent);
|
373 | content = newContent;
|
374 | }
|
375 | var sourceEnd = Math.min(offset + length, buffer.length);
|
376 | var written = buffer.copy(content, position, offset, sourceEnd);
|
377 | file.setContent(content);
|
378 | descriptor.setPosition(newLength);
|
379 | return written;
|
380 | });
|
381 | };
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 | Binding.prototype.rename = function(oldPath, newPath, callback) {
|
392 | return maybeCallback(callback, this, function() {
|
393 | var oldItem = this._system.getItem(oldPath);
|
394 | if (!oldItem) {
|
395 | throw noSuchFile(oldPath, 'rename');
|
396 | }
|
397 | var oldName = oldItem.getName();
|
398 | var newItem = this._system.getItem(newPath);
|
399 | var newParent, newName;
|
400 | if (newItem) {
|
401 |
|
402 | if (oldItem instanceof File) {
|
403 | if (newItem instanceof Directory) {
|
404 |
|
405 | throw new Error('EISDIR, illegal operation on a directory');
|
406 | }
|
407 | } else if (oldItem instanceof Directory) {
|
408 | if (!(newItem instanceof Directory)) {
|
409 | throw new Error('ENOTDIR, not a directory');
|
410 | }
|
411 | if (newItem.list().length > 0) {
|
412 | throw new Error('ENOTEMPTY, directory not empty');
|
413 | }
|
414 | }
|
415 | newParent = newItem.getParent();
|
416 | newName = newItem.getName();
|
417 | newParent.removeItem(newName);
|
418 | } else {
|
419 | newParent = this._system.getItem(path.dirname(newPath));
|
420 | if (!newParent) {
|
421 | throw new Error('ENOENT, no such file or directory');
|
422 | }
|
423 | if (!(newParent instanceof Directory)) {
|
424 | throw new Error('ENOTDIR, not a directory');
|
425 | }
|
426 | newName = path.basename(newPath);
|
427 | }
|
428 | oldItem.getParent().removeItem(oldName);
|
429 | newParent.addItem(oldItem);
|
430 | newParent.renameItem(oldName, newName);
|
431 | });
|
432 | };
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 | Binding.prototype.readdir = function(dirpath, callback) {
|
443 | return maybeCallback(callback, this, function() {
|
444 | var dir = this._system.getItem(dirpath);
|
445 | if (!dir) {
|
446 | throw new Error('ENOENT, no such file or directory');
|
447 | }
|
448 | if (!(dir instanceof Directory)) {
|
449 | throw new Error('ENOTDIR, not a directory');
|
450 | }
|
451 | return dir.list();
|
452 | });
|
453 | };
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 | Binding.prototype.mkdir = function(pathname, mode, callback) {
|
463 | maybeCallback(callback, this, function() {
|
464 | var item = this._system.getItem(pathname);
|
465 | if (item) {
|
466 | throw new Error('EEXIST, file already exists');
|
467 | }
|
468 | var parent = this._system.getItem(path.dirname(pathname));
|
469 | if (!parent) {
|
470 | throw new Error('ENOENT, no such file or directory');
|
471 | }
|
472 | var dir = new Directory(path.basename(pathname));
|
473 | dir.setMode(mode);
|
474 | parent.addItem(dir);
|
475 | });
|
476 | };
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 | Binding.prototype.rmdir = function(pathname, callback) {
|
485 | maybeCallback(callback, this, function() {
|
486 | var item = this._system.getItem(pathname);
|
487 | if (!item) {
|
488 | throw new Error('ENOENT, no such file or directory');
|
489 | }
|
490 | if (!(item instanceof Directory)) {
|
491 | throw new Error('ENOTDIR, not a directory');
|
492 | }
|
493 | if (item.list().length > 0) {
|
494 | throw new Error('ENOTEMPTY, directory not empty');
|
495 | }
|
496 | item.getParent().removeItem(item.getName());
|
497 | });
|
498 | };
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 | Binding.prototype.ftruncate = function(fd, len, callback) {
|
508 | maybeCallback(callback, this, function() {
|
509 | var descriptor = this._getDescriptorById(fd);
|
510 | if (!descriptor.isWrite()) {
|
511 | throw new Error('EINVAL, invalid argument');
|
512 | }
|
513 | var file = descriptor.getItem();
|
514 | if (!(file instanceof File)) {
|
515 | throw new Error('EINVAL, invalid argument');
|
516 | }
|
517 | var content = file.getContent();
|
518 | var newContent = new Buffer(len);
|
519 | content.copy(newContent);
|
520 | file.setContent(newContent);
|
521 | });
|
522 | };
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | Binding.prototype.truncate = Binding.prototype.ftruncate;
|
532 |
|
533 |
|
534 |
|
535 |
|
536 |
|
537 |
|
538 |
|
539 |
|
540 |
|
541 | Binding.prototype.chown = function(pathname, uid, gid, callback) {
|
542 | maybeCallback(callback, this, function() {
|
543 | var item = this._system.getItem(pathname);
|
544 | if (!item) {
|
545 | throw new Error('ENOENT, no such file or directory');
|
546 | }
|
547 | item.setUid(uid);
|
548 | item.setGid(gid);
|
549 | });
|
550 | };
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 | Binding.prototype.fchown = function(fd, uid, gid, callback) {
|
561 | maybeCallback(callback, this, function() {
|
562 | var descriptor = this._getDescriptorById(fd);
|
563 | var item = descriptor.getItem();
|
564 | item.setUid(uid);
|
565 | item.setGid(gid);
|
566 | });
|
567 | };
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 | Binding.prototype.chmod = function(pathname, mode, callback) {
|
577 | maybeCallback(callback, this, function() {
|
578 | var item = this._system.getItem(pathname);
|
579 | if (!item) {
|
580 | throw new Error('ENOENT, no such file or directory');
|
581 | }
|
582 | item.setMode(mode);
|
583 | });
|
584 | };
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 | Binding.prototype.fchmod = function(fd, mode, callback) {
|
594 | maybeCallback(callback, this, function() {
|
595 | var descriptor = this._getDescriptorById(fd);
|
596 | var item = descriptor.getItem();
|
597 | item.setMode(mode);
|
598 | });
|
599 | };
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 |
|
607 | Binding.prototype.unlink = function(pathname, callback) {
|
608 | maybeCallback(callback, this, function() {
|
609 | var item = this._system.getItem(pathname);
|
610 | if (!item) {
|
611 | throw new Error('ENOENT, no such file or directory');
|
612 | }
|
613 | if (item instanceof Directory) {
|
614 | throw new Error('EPERM, operation not permitted');
|
615 | }
|
616 | var parent = item.getParent();
|
617 | parent.removeItem(item.getName());
|
618 | });
|
619 | };
|