#include "node.h" #include #include #include #include #include #include #include using namespace v8; #define FD_SYMBOL v8::String::NewSymbol("fd") #define ACTION_QUEUE_SYMBOL v8::String::NewSymbol("_actionQueue") // This is the file system object which contains methods // for accessing the file system (like rename, mkdir, etC). // In javascript it is called "File". static Persistent fs; class FileSystem { public: static Handle Rename (const Arguments& args); static int AfterRename (eio_req *req); static Handle Stat (const Arguments& args); static int AfterStat (eio_req *req); static Handle StrError (const Arguments& args); }; class File { public: File (Handle handle); ~File (); static Handle New (const Arguments& args); static Handle Open (const Arguments& args); static int AfterOpen (eio_req *req); static Handle Close (const Arguments& args); static int AfterClose (eio_req *req); static Handle Write (const Arguments& args); static int AfterWrite (eio_req *req); static Handle Read (const Arguments& args); static int AfterRead (eio_req *req); private: static File* Unwrap (Handle handle); bool HasUtf8Encoding (void); int GetFD (void); static void MakeWeak (Persistent _, void *data); Persistent handle_; }; static void CallTopCallback (Handle handle, const int argc, Handle argv[]) { HandleScope scope; Local queue_value = handle->Get(ACTION_QUEUE_SYMBOL); assert(queue_value->IsArray()); Local queue = Local::Cast(queue_value); Local top_value = queue->Get(Integer::New(0)); if (top_value->IsObject()) { Local top = top_value->ToObject(); Local callback_value = top->Get(String::NewSymbol("callback")); if (callback_value->IsFunction()) { Handle callback = Handle::Cast(callback_value); TryCatch try_catch; callback->Call(handle, argc, argv); if(try_catch.HasCaught()) { node_fatal_exception(try_catch); return; } } } // poll_actions Local poll_actions_value = handle->Get(String::NewSymbol("_pollActions")); assert(poll_actions_value->IsFunction()); Handle poll_actions = Handle::Cast(poll_actions_value); poll_actions->Call(handle, 0, NULL); } static void InitActionQueue (Handle handle) { handle->Set(ACTION_QUEUE_SYMBOL, Array::New()); } Handle FileSystem::Rename (const Arguments& args) { if (args.Length() < 2) return Undefined(); HandleScope scope; String::Utf8Value path(args[0]->ToString()); String::Utf8Value new_path(args[1]->ToString()); node_eio_warmup(); eio_req *req = eio_rename(*path, *new_path, EIO_PRI_DEFAULT, AfterRename, NULL); return Undefined(); } int FileSystem::AfterRename (eio_req *req) { HandleScope scope; const int argc = 1; Local argv[argc]; argv[0] = Integer::New(req->errorno); CallTopCallback(fs, argc, argv); return 0; } Handle FileSystem::Stat (const Arguments& args) { if (args.Length() < 1) return v8::Undefined(); HandleScope scope; String::Utf8Value path(args[0]->ToString()); node_eio_warmup(); eio_req *req = eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, NULL); return Undefined(); } int FileSystem::AfterStat (eio_req *req) { HandleScope scope; const int argc = 2; Local argv[argc]; argv[0] = Integer::New(req->errorno); Local stats = Object::New(); argv[1] = stats; if (req->result == 0) { struct stat *s = static_cast(req->ptr2); /* ID of device containing file */ stats->Set(JS_SYMBOL("dev"), Integer::New(s->st_dev)); /* inode number */ stats->Set(JS_SYMBOL("ino"), Integer::New(s->st_ino)); /* protection */ stats->Set(JS_SYMBOL("mode"), Integer::New(s->st_mode)); /* number of hard links */ stats->Set(JS_SYMBOL("nlink"), Integer::New(s->st_nlink)); /* user ID of owner */ stats->Set(JS_SYMBOL("uid"), Integer::New(s->st_uid)); /* group ID of owner */ stats->Set(JS_SYMBOL("gid"), Integer::New(s->st_gid)); /* device ID (if special file) */ stats->Set(JS_SYMBOL("rdev"), Integer::New(s->st_rdev)); /* total size, in bytes */ stats->Set(JS_SYMBOL("size"), Integer::New(s->st_size)); /* blocksize for filesystem I/O */ stats->Set(JS_SYMBOL("blksize"), Integer::New(s->st_blksize)); /* number of blocks allocated */ stats->Set(JS_SYMBOL("blocks"), Integer::New(s->st_blocks)); /* time of last access */ stats->Set(JS_SYMBOL("atime"), Date::New(1000*static_cast(s->st_atime))); /* time of last modification */ stats->Set(JS_SYMBOL("mtime"), Date::New(1000*static_cast(s->st_mtime))); /* time of last status change */ stats->Set(JS_SYMBOL("ctime"), Date::New(1000*static_cast(s->st_ctime))); } CallTopCallback(fs, argc, argv); return 0; } Handle FileSystem::StrError (const Arguments& args) { if (args.Length() < 1) return v8::Undefined(); if (!args[0]->IsNumber()) return v8::Undefined(); HandleScope scope; int errorno = args[0]->IntegerValue(); Local message = String::New(strerror(errorno)); return scope.Close(message); } ///////////////////// FILE ///////////////////// File::File (Handle handle) { HandleScope scope; handle_ = Persistent::New(handle); InitActionQueue(handle); Handle external = External::New(this); handle_->SetInternalField(0, external); handle_.MakeWeak(this, File::MakeWeak); } File::~File () { // XXX call close? handle_->SetInternalField(0, Undefined()); handle_.Dispose(); handle_.Clear(); } File* File::Unwrap (Handle handle) { HandleScope scope; Handle field = Handle::Cast(handle->GetInternalField(0)); File* file = static_cast(field->Value()); return file; } bool File::HasUtf8Encoding (void) { return false; } int File::GetFD (void) { Handle fd_value = handle_->Get(FD_SYMBOL); int fd = fd_value->IntegerValue(); } void File::MakeWeak (Persistent _, void *data) { File *file = static_cast (data); delete file; } Handle File::Close (const Arguments& args) { HandleScope scope; File *file = File::Unwrap(args.Holder()); int fd = file->GetFD(); node_eio_warmup(); eio_req *req = eio_close (fd, EIO_PRI_DEFAULT, File::AfterClose, file); return Undefined(); } int File::AfterClose (eio_req *req) { File *file = static_cast(req->data); if (req->result == 0) { file->handle_->Delete(FD_SYMBOL); } const int argc = 1; Local argv[argc]; argv[0] = Integer::New(req->errorno); CallTopCallback(file->handle_, argc, argv); return 0; } Handle File::Open (const Arguments& args) { /* check arguments */ if (args.Length() < 1) return Undefined(); if (!args[0]->IsString()) return Undefined(); HandleScope scope; File *file = File::Unwrap(args.Holder()); // make sure that we don't already have a pending open if (file->handle_->Has(FD_SYMBOL)) { return ThrowException(String::New("File object is opened.")); } String::Utf8Value path(args[0]->ToString()); int flags = O_RDONLY; // default if (args[1]->IsString()) { String::AsciiValue mode_v(args[1]->ToString()); char *mode = *mode_v; // XXX is this interpretation of the mode correct? // I don't want to to use fopen() directly because eio doesn't support it. switch(mode[0]) { case 'r': flags = (mode[1] == '+' ? O_RDWR : O_RDONLY); break; case 'w': flags = O_CREAT | O_TRUNC | (mode[1] == '+' ? O_RDWR : O_WRONLY); break; case 'a': flags = O_APPEND | O_CREAT | (mode[1] == '+' ? O_RDWR : O_WRONLY); break; } } // TODO how should the mode be set? node_eio_warmup(); eio_req *req = eio_open (*path, flags, 0666, EIO_PRI_DEFAULT, File::AfterOpen, file); return Undefined(); } int File::AfterOpen (eio_req *req) { File *file = static_cast(req->data); HandleScope scope; if(req->result >= 0) { file->handle_->Set(FD_SYMBOL, Integer::New(req->result)); } const int argc = 1; Handle argv[argc]; argv[0] = Integer::New(req->errorno); CallTopCallback(file->handle_, argc, argv); return 0; } Handle File::Write (const Arguments& args) { if (args.Length() < 1) return Undefined(); if (!args[0]->IsString()) HandleScope scope; File *file = File::Unwrap(args.Holder()); char *buf = NULL; size_t length = 0; if (args[0]->IsString()) { // utf8 encoded data Local string = args[0]->ToString(); length = string->Utf8Length(); buf = new char[length]; string->WriteUtf8(buf, length); } else if (args[0]->IsArray()) { // binary data Local array = Local::Cast(args[0]); length = array->Length(); buf = new char[length]; for (int i = 0; i < length; i++) { Local int_value = array->Get(Integer::New(i)); buf[i] = int_value->Int32Value(); } } else { // bad arguments. raise error? return Undefined(); } if (file->handle_->Has(FD_SYMBOL) == false) { printf("trying to write to a bad fd!\n"); return Undefined(); } int fd = file->GetFD(); // NOTE: -1 offset in eio_write() invokes write() instead of pwrite() node_eio_warmup(); eio_req *req = eio_write(fd, buf, length, -1, EIO_PRI_DEFAULT, File::AfterWrite, file); return Undefined(); } int File::AfterWrite (eio_req *req) { File *file = static_cast(req->data); char *buf = static_cast(req->ptr2); delete buf; size_t written = req->result; HandleScope scope; const int argc = 2; Local argv[argc]; argv[0] = Integer::New(req->errorno); argv[1] = written >= 0 ? Integer::New(written) : Integer::New(0); CallTopCallback(file->handle_, argc, argv); return 0; } Handle File::Read (const Arguments& args) { if (args.Length() < 1) return Undefined(); if (!args[0]->IsNumber()) return Undefined(); HandleScope scope; File *file = File::Unwrap(args.Holder()); size_t length = args[0]->IntegerValue(); int fd = file->GetFD(); // NOTE: -1 offset in eio_read() invokes read() instead of pread() // NULL pointer tells eio to allocate it itself node_eio_warmup(); eio_req *req = eio_read(fd, NULL, length, -1, EIO_PRI_DEFAULT, File::AfterRead, file); assert(req); return Undefined(); } int File::AfterRead (eio_req *req) { File *file = static_cast(req->data); HandleScope scope; const int argc = 2; Local argv[argc]; argv[0] = Integer::New(req->errorno); char *buf = static_cast(req->ptr2); if(req->result == 0) { // eof argv[1] = Local::New(Null()); } else { size_t length = req->result; if (file->HasUtf8Encoding()) { // utf8 encoding argv[1] = String::New(buf, req->result); } else { // raw encoding Local array = Array::New(length); for (int i = 0; i < length; i++) { array->Set(Integer::New(i), Integer::New(buf[i])); } argv[1] = array; } } CallTopCallback(file->handle_, argc, argv); return 0; } Handle File::New(const Arguments& args) { HandleScope scope; File *file = new File(args.Holder()); if(file == NULL) return Undefined(); // XXX raise error? return args.This(); } void NodeInit_file (Handle target) { if (!fs.IsEmpty()) return; HandleScope scope; Local file_template = FunctionTemplate::New(File::New); file_template->InstanceTemplate()->SetInternalFieldCount(1); fs = Persistent::New(file_template->GetFunction()); InitActionQueue(fs); target->Set(String::NewSymbol("File"), fs); // file system methods JS_SET_METHOD(fs, "_ffi_rename", FileSystem::Rename); JS_SET_METHOD(fs, "_ffi_stat", FileSystem::Stat); JS_SET_METHOD(fs, "strerror", FileSystem::StrError); fs->Set(String::NewSymbol("STDIN_FILENO"), Integer::New(STDIN_FILENO)); fs->Set(String::NewSymbol("STDOUT_FILENO"), Integer::New(STDOUT_FILENO)); fs->Set(String::NewSymbol("STDERR_FILENO"), Integer::New(STDERR_FILENO)); // file methods JS_SET_METHOD(file_template->InstanceTemplate(), "_ffi_open", File::Open); JS_SET_METHOD(file_template->InstanceTemplate(), "_ffi_close", File::Close); JS_SET_METHOD(file_template->InstanceTemplate(), "_ffi_write", File::Write); JS_SET_METHOD(file_template->InstanceTemplate(), "_ffi_read", File::Read); }