Skip to content

Commit 7ca77ea

Browse files
committed
fs: write strings directly to disk
Prior, strings would first be converted to a Buffer before being written to disk. Now the intermediary step has been removed. Other changes of note: * Class member "must_free" was added to req_wrap so to track if the memory needs to be manually cleaned up after use. * External String Resource support, so the memory will be used directly instead of copying out the data. * Docs have been updated to reflect that if position is not a number then it will assume null. Previously it specified the argument must be null, but that was not how the code worked. An attempt was made to only support == null, but there were too many tests that assumed != number would be enough. * Docs update show some of the write/writeSync arguments are optional.
1 parent 63fc6a6 commit 7ca77ea

File tree

4 files changed

+180
-73
lines changed

4 files changed

+180
-73
lines changed

doc/api/fs.markdown

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,19 +373,18 @@ to the completion callback.
373373

374374
Synchronous fsync(2).
375375

376-
## fs.write(fd, buffer, offset, length, position, callback)
376+
## fs.write(fd, buffer, offset, length[, position], callback)
377377

378378
Write `buffer` to the file specified by `fd`.
379379

380380
`offset` and `length` determine the part of the buffer to be written.
381381

382382
`position` refers to the offset from the beginning of the file where this data
383-
should be written. If `position` is `null`, the data will be written at the
384-
current position.
385-
See pwrite(2).
383+
should be written. If `typeof position !== 'number'`, the data will be written
384+
at the current position. See pwrite(2).
386385

387-
The callback will be given three arguments `(err, written, buffer)` where `written`
388-
specifies how many _bytes_ were written from `buffer`.
386+
The callback will be given three arguments `(err, written, buffer)` where
387+
`written` specifies how many _bytes_ were written from `buffer`.
389388

390389
Note that it is unsafe to use `fs.write` multiple times on the same file
391390
without waiting for the callback. For this scenario,
@@ -395,9 +394,39 @@ On Linux, positional writes don't work when the file is opened in append mode.
395394
The kernel ignores the position argument and always appends the data to
396395
the end of the file.
397396

398-
## fs.writeSync(fd, buffer, offset, length, position)
397+
## fs.write(fd, data[, position[, encoding]], callback)
399398

400-
Synchronous version of `fs.write()`. Returns the number of bytes written.
399+
Write `data` to the file specified by `fd`. If `data` is not a Buffer instance
400+
then the value will be coerced to a string.
401+
402+
`position` refers to the offset from the beginning of the file where this data
403+
should be written. If `typeof position !== 'number'` the data will be written at
404+
the current position. See pwrite(2).
405+
406+
`encoding` is the expected string encoding.
407+
408+
The callback will receive the arguments `(err, written, string)` where `written`
409+
specifies how many _bytes_ the passed string required to be written. Note that
410+
bytes written is not the same as string characters. See
411+
[Buffer.byteLength](buffer.html#buffer_class_method_buffer_bytelength_string_encoding).
412+
413+
Unlike when writing `buffer`, the entire string must be written. No substring
414+
may be specified. This is because the byte offset of the resulting data may not
415+
be the same as the string offset.
416+
417+
Note that it is unsafe to use `fs.write` multiple times on the same file
418+
without waiting for the callback. For this scenario,
419+
`fs.createWriteStream` is strongly recommended.
420+
421+
On Linux, positional writes don't work when the file is opened in append mode.
422+
The kernel ignores the position argument and always appends the data to
423+
the end of the file.
424+
425+
## fs.writeSync(fd, buffer, offset, length[, position])
426+
427+
## fs.writeSync(fd, data[, position[, encoding]])
428+
429+
Synchronous versions of `fs.write()`. Returns the number of bytes written.
401430

402431
## fs.read(fd, buffer, offset, length, position, callback)
403432

lib/fs.js

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -462,50 +462,59 @@ fs.readSync = function(fd, buffer, offset, length, position) {
462462
return [str, r];
463463
};
464464

465+
// usage:
466+
// fs.write(fd, buffer, offset, length[, position], callback);
467+
// OR
468+
// fs.write(fd, string[, position[, encoding]], callback);
465469
fs.write = function(fd, buffer, offset, length, position, callback) {
466-
if (!IS_BUFFER(buffer)) {
467-
// legacy string interface (fd, data, position, encoding, callback)
468-
callback = arguments[4];
469-
position = arguments[2];
470-
assertEncoding(arguments[3]);
471-
472-
buffer = new Buffer('' + arguments[1], arguments[3]);
473-
offset = 0;
474-
length = buffer.length;
470+
if (IS_BUFFER(buffer)) {
471+
// if no position is passed then assume null
472+
if (IS_FUNCTION(position)) {
473+
callback = position;
474+
position = null;
475+
}
476+
callback = maybeCallback(callback);
477+
var wrapper = function(err, written) {
478+
// Retain a reference to buffer so that it can't be GC'ed too soon.
479+
callback(err, written || 0, buffer);
480+
};
481+
return binding.writeBuffer(fd, buffer, offset, length, position, wrapper);
475482
}
476483

477-
if (!length) {
478-
if (IS_FUNCTION(callback)) {
479-
process.nextTick(function() {
480-
callback(undefined, 0);
481-
});
484+
if (IS_STRING(buffer))
485+
buffer += '';
486+
if (!IS_FUNCTION(position)) {
487+
if (IS_FUNCTION(offset)) {
488+
position = offset;
489+
offset = null;
490+
} else {
491+
position = length;
482492
}
483-
return;
493+
length = 'utf8';
484494
}
485-
486-
callback = maybeCallback(callback);
487-
488-
function wrapper(err, written) {
489-
// Retain a reference to buffer so that it can't be GC'ed too soon.
495+
callback = maybeCallback(position);
496+
position = function(err, written) {
497+
// retain reference to string in case it's external
490498
callback(err, written || 0, buffer);
491-
}
492-
493-
binding.write(fd, buffer, offset, length, position, wrapper);
499+
};
500+
return binding.writeString(fd, buffer, offset, length, position);
494501
};
495502

503+
// usage:
504+
// fs.writeSync(fd, buffer, offset, length[, position]);
505+
// OR
506+
// fs.writeSync(fd, string[, position[, encoding]]);
496507
fs.writeSync = function(fd, buffer, offset, length, position) {
497-
if (!IS_BUFFER(buffer)) {
498-
// legacy string interface (fd, data, position, encoding)
499-
position = arguments[2];
500-
assertEncoding(arguments[3]);
501-
502-
buffer = new Buffer('' + arguments[1], arguments[3]);
503-
offset = 0;
504-
length = buffer.length;
505-
}
506-
if (!length) return 0;
507-
508-
return binding.write(fd, buffer, offset, length, position);
508+
if (IS_BUFFER(buffer)) {
509+
if (IS_UNDEFINED(position))
510+
position = null;
511+
return binding.writeBuffer(fd, buffer, offset, length, position);
512+
}
513+
if (!IS_STRING(buffer))
514+
buffer += '';
515+
if (IS_UNDEFINED(offset))
516+
offset = null;
517+
return binding.writeString(fd, buffer, offset, length, position);
509518
};
510519

511520
fs.rename = function(oldPath, newPath, callback) {

src/node_file.cc

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
#include "node.h"
2323
#include "node_file.h"
2424
#include "node_buffer.h"
25+
#include "node_internals.h"
2526
#include "node_stat_watcher.h"
2627
#include "req_wrap.h"
28+
#include "string_bytes.h"
2729

2830
#include <fcntl.h>
2931
#include <sys/types.h>
@@ -62,10 +64,12 @@ using v8::Value;
6264
class FSReqWrap: public ReqWrap<uv_fs_t> {
6365
public:
6466
FSReqWrap(const char* syscall)
65-
: syscall_(syscall) {
67+
: must_free_(false),
68+
syscall_(syscall) {
6669
}
6770

6871
const char* syscall() { return syscall_; }
72+
bool must_free_; // request is responsible for free'ing memory oncomplete
6973

7074
private:
7175
const char* syscall_;
@@ -97,6 +101,10 @@ static void After(uv_fs_t *req) {
97101
FSReqWrap* req_wrap = (FSReqWrap*) req->data;
98102
assert(&req_wrap->req_ == req);
99103

104+
// check if data needs to be cleaned
105+
if (req_wrap->must_free_ == true)
106+
delete[] static_cast<char*>(req_wrap->data_);
107+
100108
// there is always at least one argument. "error"
101109
int argc = 1;
102110

@@ -485,7 +493,7 @@ static void Rename(const FunctionCallbackInfo<Value>& args) {
485493
if (len < 2) return TYPE_ERROR("new path required");
486494
if (!args[0]->IsString()) return TYPE_ERROR("old path must be a string");
487495
if (!args[1]->IsString()) return TYPE_ERROR("new path must be a string");
488-
496+
489497
String::Utf8Value old_path(args[0]);
490498
String::Utf8Value new_path(args[1]);
491499

@@ -650,56 +658,114 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
650658
}
651659
}
652660

653-
// bytesWritten = write(fd, data, position, enc, callback)
661+
654662
// Wrapper for write(2).
655663
//
664+
// bytesWritten = write(fd, buffer, offset, length, position, callback)
656665
// 0 fd integer. file descriptor
657666
// 1 buffer the data to write
658667
// 2 offset where in the buffer to start from
659668
// 3 length how much to write
660669
// 4 position if integer, position to write at in the file.
661670
// if null, write from the current position
662-
static void Write(const FunctionCallbackInfo<Value>& args) {
671+
static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
663672
HandleScope scope(node_isolate);
664673

665-
if (!args[0]->IsInt32()) {
666-
return THROW_BAD_ARGS;
667-
}
674+
assert(args[0]->IsInt32());
675+
assert(Buffer::HasInstance(args[1]));
668676

669677
int fd = args[0]->Int32Value();
678+
Local<Object> obj = args[1].As<Object>();
679+
const char* buf = Buffer::Data(obj);
680+
size_t buffer_length = Buffer::Length(obj);
681+
size_t off = args[2]->Uint32Value();
682+
size_t len = args[3]->Uint32Value();
683+
int64_t pos = GET_OFFSET(args[4]);
684+
Local<Value> cb = args[5];
670685

671-
if (!Buffer::HasInstance(args[1])) {
672-
return ThrowError("Second argument needs to be a buffer");
673-
}
686+
if (off > buffer_length)
687+
return ThrowRangeError("offset out of bounds");
688+
if (len > buffer_length)
689+
return ThrowRangeError("length out of bounds");
690+
if (off + len < off)
691+
return ThrowRangeError("off + len overflow");
692+
if (off + len > buffer_length)
693+
return ThrowRangeError("off + len > buffer.length");
674694

675-
Local<Object> buffer_obj = args[1]->ToObject();
676-
char *buffer_data = Buffer::Data(buffer_obj);
677-
size_t buffer_length = Buffer::Length(buffer_obj);
695+
buf += off;
678696

679-
size_t off = args[2]->Int32Value();
680-
if (off >= buffer_length) {
681-
return ThrowError("Offset is out of bounds");
697+
if (cb->IsFunction()) {
698+
ASYNC_CALL(write, cb, fd, buf, len, pos)
699+
return;
682700
}
683701

684-
ssize_t len = args[3]->Int32Value();
685-
if (off + len > buffer_length) {
686-
return ThrowError("off + len > buffer.length");
687-
}
702+
SYNC_CALL(write, NULL, fd, buf, len, pos)
703+
args.GetReturnValue().Set(SYNC_RESULT);
704+
}
688705

689-
ASSERT_OFFSET(args[4]);
690-
int64_t pos = GET_OFFSET(args[4]);
691706

692-
char * buf = (char*)buffer_data + off;
693-
Local<Value> cb = args[5];
707+
// Wrapper for write(2).
708+
//
709+
// bytesWritten = write(fd, string, position, enc, callback)
710+
// 0 fd integer. file descriptor
711+
// 1 string non-buffer values are converted to strings
712+
// 2 position if integer, position to write at in the file.
713+
// if null, write from the current position
714+
// 3 enc encoding of string
715+
static void WriteString(const FunctionCallbackInfo<Value>& args) {
716+
HandleScope scope(node_isolate);
717+
718+
if (!args[0]->IsInt32())
719+
return ThrowTypeError("First argument must be file descriptor");
720+
721+
Local<Value> cb;
722+
Local<Value> string = args[1];
723+
int fd = args[0]->Int32Value();
724+
char* buf = NULL;
725+
int64_t pos;
726+
size_t len;
727+
bool must_free_ = false;
728+
729+
// will assign buf and len if string was external
730+
if (!StringBytes::GetExternalParts(string,
731+
const_cast<const char**>(&buf),
732+
&len)) {
733+
enum encoding enc = ParseEncoding(args[3], UTF8);
734+
len = StringBytes::StorageSize(string, enc);
735+
buf = new char[len];
736+
// StorageSize may return too large a char, so correct the actual length
737+
// by the write size
738+
len = StringBytes::Write(buf, len, args[1], enc);
739+
must_free_ = true;
740+
}
741+
pos = GET_OFFSET(args[2]);
742+
cb = args[4];
694743

695744
if (cb->IsFunction()) {
696-
ASYNC_CALL(write, cb, fd, buf, len, pos)
697-
} else {
698-
SYNC_CALL(write, 0, fd, buf, len, pos)
699-
args.GetReturnValue().Set(SYNC_RESULT);
745+
FSReqWrap* req_wrap = new FSReqWrap("write");
746+
int err = uv_fs_write(uv_default_loop(), &req_wrap->req_,
747+
fd, buf, len, pos, After);
748+
req_wrap->object()->Set(oncomplete_sym, cb);
749+
req_wrap->must_free_ = must_free_;
750+
req_wrap->Dispatched();
751+
req_wrap->data_ = buf;
752+
if (err < 0) {
753+
uv_fs_t* req = &req_wrap->req_;
754+
req->result = err;
755+
req->path = NULL;
756+
After(req);
757+
}
758+
return args.GetReturnValue().Set(req_wrap->persistent());
700759
}
760+
761+
SYNC_CALL(write, NULL, fd, buf, len, pos)
762+
args.GetReturnValue().Set(SYNC_RESULT);
763+
764+
if (must_free_)
765+
delete[] buf;
701766
}
702767

768+
703769
/*
704770
* Wrapper for read(2).
705771
*
@@ -918,7 +984,8 @@ void File::Initialize(Handle<Object> target) {
918984
NODE_SET_METHOD(target, "symlink", Symlink);
919985
NODE_SET_METHOD(target, "readlink", ReadLink);
920986
NODE_SET_METHOD(target, "unlink", Unlink);
921-
NODE_SET_METHOD(target, "write", Write);
987+
NODE_SET_METHOD(target, "writeBuffer", WriteBuffer);
988+
NODE_SET_METHOD(target, "writeString", WriteString);
922989

923990
NODE_SET_METHOD(target, "chmod", Chmod);
924991
NODE_SET_METHOD(target, "fchmod", FChmod);

src/string_bytes.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ bool StringBytes::GetExternalParts(Handle<Value> val,
239239
return true;
240240
}
241241

242-
assert(val->IsString());
242+
if (!val->IsString())
243+
return false;
244+
243245
Local<String> str = Local<String>::New(val.As<String>());
244246

245247
if (str->IsExternalAscii()) {

0 commit comments

Comments
 (0)