--- /dev/null
+/* $Id: js_file.c,v 1.1 2006/09/25 23:06:23 mmondor Exp $ */
+
+/*
+ * Copyright (c) 2006, Matthew Mondor
+ * ALL RIGHTS RESERVED.
+ */
+
+/*
+ * Since many libraries support working with FILE * rather than file
+ * desdcriptors, it was necessary to provide stdio functionality.
+ * The file_new() function is exported so that other modules may create file
+ * handles.
+ *
+ * XXX
+ * - add path validity checking hook function
+ * - optionally make the class init function also initialize stdin, stdout,
+ * stderr
+ * - implement formatted printing functions sprintf, fprintf, printf
+ */
+
+
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <jsapi.h>
+
+#include <js_file.h>
+
+
+
+#define QUEUE_EXCEPTION(s) do { \
+ JS_SetPendingException(cx, \
+ STRING_TO_JSVAL(JS_NewStringCopyZ(cx, (s)))); \
+} while (/* CONSTCOND */0)
+
+#define SP(n) \
+ { #n, n }
+
+struct property_spec {
+ const char *name;
+ int value;
+};
+
+enum file_flags {
+ FF_NONE = 0,
+ FF_CLOSE = (1 << 1),
+ FF_PCLOSE = (1 << 2)
+};
+
+/*
+ * Although we could simply wrap around FILE * directly, we need to know if
+ * we should close the file or not upon finalization, and how to close it.
+ * We use the flags field for that, since stdio does not allow to associate
+ * user data pointers with FILE objects portably (which would have been better
+ * for performance).
+ */
+typedef struct file {
+ FILE *fh;
+ int flags;
+ char *vbuf;
+ size_t vbufsize;
+} file_t;
+
+
+
+/*
+ * Static prototypes
+ */
+
+static JSBool file_constructor(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static void file_finalize(JSContext *, JSObject *);
+
+static JSBool file_sm_popen(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_sm_tmpfile(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_sm_remove(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_sm_strerror(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+
+static JSBool file_m_close(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_reopen(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_seek(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_tell(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_rewind(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_flush(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_purge(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_setvbuf(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_clearerr(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_eof(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_error(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_fileno(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_read(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_write(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_gets(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_getc(JSContext *, JSObject *, uintN, jsval *, jsval *);
+static JSBool file_m_ungetc(JSContext *, JSObject *, uintN, jsval *,
+ jsval *);
+static JSBool file_m_putc(JSContext *, JSObject *, uintN, jsval *, jsval *);
+
+
+
+/*
+ * Static globals
+ */
+
+/*
+ * File
+ */
+
+static JSClass file_class = {
+ "File", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub,
+ JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub,
+ JS_ConvertStub, file_finalize
+};
+
+/* Static properties */
+static struct property_spec file_sprops[] = {
+ SP(SEEK_SET),
+ SP(SEEK_CUR),
+ SP(SEEK_END),
+ SP(_IONBF),
+ SP(_IOLBF),
+ SP(_IOFBF),
+ SP(STDIN_FILENO),
+ SP(STDOUT_FILENO),
+ SP(STDERR_FILENO)
+};
+
+/* Static methods */
+static JSFunctionSpec file_smethods[] = {
+ { "popen", file_sm_popen, 2, 0, 0 },
+ { "tmpfile", file_sm_tmpfile, 0, 0, 0 },
+ { "remove", file_sm_remove, 1, 0, 0 },
+ { "strerror", file_sm_strerror, 1, 0, 0 }
+};
+
+/* Methods */
+static JSFunctionSpec file_methods[] = {
+ { "close", file_m_close, 0, 0, 0 },
+ { "reopen", file_m_reopen, 2, 0, 0 },
+ { "seek", file_m_seek, 2, 0, 0 },
+ { "tell", file_m_tell, 0, 0, 0 },
+ { "rewind", file_m_rewind, 0, 0, 0 },
+ { "flush", file_m_flush, 0, 0, 0 },
+ { "purge", file_m_purge, 0, 0, 0 },
+ { "setvbuf", file_m_setvbuf, 2, 0, 0 },
+ { "clearerr", file_m_clearerr, 0, 0, 0 },
+ { "eof", file_m_eof, 0, 0, 0 },
+ { "error", file_m_error, 0, 0, 0 },
+ { "fileno", file_m_fileno, 0, 0, 0 },
+ { "read", file_m_read, 2, 0, 0 },
+ { "write", file_m_write, 1, 0, 0 },
+ { "gets", file_m_gets, 2, 0, 0 },
+ { "getc", file_m_getc, 1, 0, 0 },
+ { "ungetc", file_m_ungetc, 1, 0, 0 },
+ { "putc", file_m_putc, 1, 0, 0 }
+};
+
+/*
+ * Static globals. These could cause reentrancy problems unless they were
+ * moved to be File specific, which would however require more RAM and reduce
+ * performance.
+ */
+static char *read_charbuf = NULL;
+static size_t read_charbuf_size = 0;
+
+
+
+/*
+ * Publically exported constructor function. Returns the new File object on
+ * success or NULL on failure.
+ */
+JSObject *
+file_new(JSContext *cx, FILE *fh, int close)
+{
+ file_t *f;
+ JSObject *o;
+
+ assert(fh != NULL);
+
+ if ((f = JS_malloc(cx, sizeof(file_t))) != NULL) {
+ f->fh = fh;
+ f->flags = (close != 0 ? FF_CLOSE : 0);
+ f->vbuf = NULL;
+ f->vbufsize = 0;
+ }
+
+ if ((o = JS_NewObject(cx, &file_class, NULL, NULL)) == NULL)
+ goto err;
+ if (!JS_SetPrivate(cx, o, f))
+ goto err;
+
+ return o;
+
+err:
+ if (f != NULL) {
+ if ((f->flags & FF_CLOSE) != 0 && f->fh != NULL)
+ (void) fclose(f->fh);
+ JS_free(cx, f);
+ }
+
+ return NULL;
+}
+
+
+/*
+ * File object control
+ */
+
+JSObject *
+js_InitFileClass(JSContext *cx, JSObject *obj)
+{
+ JSObject *proto, *ctor;
+ struct property_spec *sp;
+
+ if ((proto = JS_InitClass(cx, obj, NULL, &file_class, file_constructor,
+ 0, NULL, file_methods, NULL, file_smethods)) == NULL) {
+ (void) fprintf(stderr, "Error initializing File class\n");
+ return NULL;
+ }
+
+ /* Create static properties. */
+ if ((ctor = JS_GetConstructor(cx, proto)) == NULL) {
+ (void) fprintf(stderr, "File: JS_GetConstructor == NULL\n");
+ return NULL;
+ }
+ for (sp = file_sprops; sp->name != NULL; sp++) {
+ if (JS_DefineProperty(cx, ctor, sp->name,
+ INT_TO_JSVAL(sp->value), NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT) == JS_FALSE) {
+ (void) fprintf(stderr,
+ "File: Error defining property %s\n", sp->name);
+ return NULL;
+ }
+ }
+
+ return proto;
+}
+
+/*
+ * We support a few forms:
+ * File(filename:String, mode:String); fopen(3)
+ * File(fdnum:Integer, mode:String); fdopen(3)
+ */
+static JSBool
+file_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f = NULL;
+ JSObject *o;
+
+ if (!JS_IsConstructing(cx)) {
+ QUEUE_EXCEPTION("Constructor called as a function");
+ goto err;
+ }
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ goto err;
+ }
+
+ if ((f = JS_malloc(cx, sizeof(file_t))) == NULL) {
+ QUEUE_EXCEPTION("Out of memory");
+ goto err;
+ }
+ f->flags = 0;
+ f->vbuf = NULL;
+ f->vbufsize = 0;
+
+ if (JSVAL_IS_STRING(argv[0]) && JSVAL_IS_STRING(argv[1])) {
+ char *path, *mode;
+
+ /* File(filename:String, mode:String); */
+ if ((path = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])))
+ == NULL ||
+ (mode = JS_GetStringBytes(JSVAL_TO_STRING(argv[1])))
+ == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+ f->flags = FF_CLOSE;
+ if ((f->fh = fopen(path, mode)) == NULL) {
+ QUEUE_EXCEPTION(strerror(errno));
+ goto err;
+ }
+ } else if (JSVAL_IS_INT(argv[0]) && JSVAL_IS_STRING(argv[1])) {
+ char *mode;
+
+ /* File(fdnum:Integer, mode:String); */
+ if ((mode = JS_GetStringBytes(JSVAL_TO_STRING(argv[1])))
+ == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+ f->flags = FF_CLOSE;
+ if ((f->fh = fdopen(JSVAL_TO_INT(argv[0]), mode)) == NULL) {
+ QUEUE_EXCEPTION(strerror(errno));
+ goto err;
+ }
+ } else {
+ QUEUE_EXCEPTION("Unknown method (invalid argument types)");
+ goto err;
+ }
+
+ if ((o = JS_NewObject(cx, &file_class, NULL, NULL)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+ *rval = OBJECT_TO_JSVAL(o);
+
+ if (!JS_SetPrivate(cx, o, f)) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+
+ return JS_TRUE;
+
+err:
+ *rval = OBJECT_TO_JSVAL(NULL);
+ if (f != NULL) {
+ if ((f->flags & FF_CLOSE) != 0 && f->fh != NULL)
+ (void) fclose(f->fh);
+ JS_free(cx, f);
+ }
+
+ return JS_FALSE;
+}
+
+/* ARGSUSED */
+static void
+file_finalize(JSContext *cx, JSObject *obj)
+{
+ file_t *f;
+
+ if ((f = JS_GetInstancePrivate(cx, obj, &file_class, NULL)) != NULL) {
+ if ((f->flags & FF_CLOSE) != 0 && f->fh != NULL) {
+ if ((f->flags & FF_PCLOSE) != 0)
+ (void) pclose(f->fh);
+ else
+ (void) fclose(f->fh);
+ if (f->vbuf != NULL)
+ JS_free(cx, f->vbuf);
+ }
+ (void) JS_SetPrivate(cx, obj, NULL);
+ JS_free(cx, f);
+ }
+}
+
+
+/*
+ * File object static methods
+ */
+
+static JSBool
+file_sm_popen(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f = NULL;
+ JSObject *o;
+ char *cmd, *mode;
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ goto err;
+ }
+ if (!JSVAL_IS_STRING(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not a String");
+ goto err;
+ }
+ if (!JSVAL_IS_STRING(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not a String");
+ goto err;
+ }
+
+ if ((cmd = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]))) == NULL ||
+ (mode = JS_GetStringBytes(JSVAL_TO_STRING(argv[1]))) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+
+ if ((f = JS_malloc(cx, sizeof(file_t))) == NULL) {
+ QUEUE_EXCEPTION("Out of memory");
+ goto err;
+ }
+ f->flags = FF_CLOSE | FF_PCLOSE;
+ f->vbuf = NULL;
+ f->vbufsize = 0;
+
+ if ((f->fh = popen(cmd, mode)) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ goto err;
+ }
+
+ if ((o = JS_NewObject(cx, &file_class, NULL, NULL)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+ *rval = OBJECT_TO_JSVAL(o);
+
+ if (!JS_SetPrivate(cx, o, f)) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+
+ return JS_TRUE;
+
+err:
+ *rval = OBJECT_TO_JSVAL(NULL);
+ if (f != NULL) {
+ if (f->fh != NULL)
+ (void) pclose(f->fh);
+ JS_free(cx, f);
+ }
+
+ return JS_FALSE;
+}
+
+static JSBool
+file_sm_tmpfile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f = NULL;
+ JSObject *o;
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ goto err;
+ }
+
+ if ((f = JS_malloc(cx, sizeof(file_t))) == NULL) {
+ QUEUE_EXCEPTION("Out of memory");
+ goto err;
+ }
+ f->flags = FF_CLOSE | FF_PCLOSE;
+ f->vbuf = NULL;
+ f->vbufsize = 0;
+
+ if ((f->fh = tmpfile()) == NULL) {
+ QUEUE_EXCEPTION(strerror(errno));
+ goto err;
+ }
+
+ if ((o = JS_NewObject(cx, &file_class, NULL, NULL)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+ *rval = OBJECT_TO_JSVAL(o);
+
+ if (!JS_SetPrivate(cx, o, f)) {
+ QUEUE_EXCEPTION("Internal error!");
+ goto err;
+ }
+
+ return JS_TRUE;
+
+err:
+ *rval = OBJECT_TO_JSVAL(NULL);
+ if (f != NULL) {
+ if (f->fh != NULL)
+ (void) fclose(f->fh);
+ JS_free(cx, f);
+ }
+
+ return JS_FALSE;
+}
+
+static JSBool
+file_sm_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ char *path;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 1) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_STRING(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not a String");
+ return JS_FALSE;
+ }
+ if ((path = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]))) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+
+ if (remove(path) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+/*
+ * We allow two forms; If no arguments are provided, we return the error
+ * message associated with errno. If one is specified, we return the error
+ * message associated with the provided error number.
+ */
+static JSBool
+file_sm_strerror(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ int error;
+ JSString *str;
+
+ if (argc == 0)
+ error = errno;
+ else {
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ *rval = OBJECT_TO_JSVAL(NULL);
+ return JS_FALSE;
+ }
+ error = JSVAL_TO_INT(argv[0]);
+ }
+
+ if ((str = JS_NewStringCopyZ(cx, strerror(error))) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ *rval = OBJECT_TO_JSVAL(NULL);
+ return JS_FALSE;
+ }
+ *rval = STRING_TO_JSVAL(str);
+
+ return JS_TRUE;
+}
+
+
+/*
+ * File object methods
+ */
+
+static JSBool
+file_m_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+ int ret;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+
+ if ((f->flags & FF_CLOSE) != 0 && f->fh != NULL) {
+ if ((f->flags & FF_PCLOSE) != 0)
+ ret = pclose(f->fh);
+ else
+ ret = fclose(f->fh);
+ f->fh = NULL;
+ } else
+ ret = 0;
+ if (ret == -1) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_reopen(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+ char *path, *mode;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ if (!JSVAL_IS_STRING(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not a String");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_STRING(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not a String");
+ return JS_FALSE;
+ }
+
+ if ((path = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]))) == NULL ||
+ (mode = JS_GetStringBytes(JSVAL_TO_STRING(argv[1]))) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if ((f->fh = freopen(path, mode, f->fh)) == NULL) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_seek(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+ jsdouble v;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_NUMBER(argv[0]) || !JS_ValueToNumber(cx, argv[0], &v)) {
+ QUEUE_EXCEPTION("Argument 1 not a number");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not an Integer");
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if (fseeko(f->fh, (off_t)v, JSVAL_TO_INT(argv[1])) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_tell(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+ off_t v;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if ((v = ftello(f->fh)) == -1) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ if (!JS_NewNumberValue(cx, (jsdouble)v, rval)) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_rewind(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ rewind(f->fh);
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_flush(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if (fflush(f->fh) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_purge(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if (fpurge(f->fh) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+/*
+ * Unlike stdio setvbuf(3), requires two arguments. One setting the buffering
+ * type, and the other the size of the wanted buffer, or 0 to use the default
+ * internal buffer.
+ * So our syntax is as follows: file.setvbuf(type:Integer, bufsize:Integer);
+ */
+static JSBool
+file_m_setvbuf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+ size_t size;
+ char *vbuf = NULL;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not an Integer");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if ((size = JSVAL_TO_INT(argv[1])) > 0) {
+ if (f->vbuf == NULL) {
+ if ((f->vbuf = JS_malloc(cx, size)) == NULL) {
+ QUEUE_EXCEPTION("Out of memory");
+ return JS_FALSE;
+ }
+ f->vbufsize = size;
+ vbuf = f->vbuf;
+ } else if (size != f->vbufsize) {
+ if ((vbuf = JS_realloc(cx, f->vbuf, size)) == NULL) {
+ QUEUE_EXCEPTION("Out of memory");
+ return JS_FALSE;
+ }
+ f->vbuf = vbuf;
+ f->vbufsize = size;
+ }
+ }
+
+ if (setvbuf(f->fh, vbuf, JSVAL_TO_INT(argv[0]), size) != 0) {
+ QUEUE_EXCEPTION("setvbuf() == EOF");
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_clearerr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ clearerr(f->fh);
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_eof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ *rval = JSVAL_TO_BOOLEAN(feof(f->fh));
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_error(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ *rval = JSVAL_TO_BOOLEAN(ferror(f->fh));
+
+ return JS_TRUE;
+}
+
+static JSBool
+file_m_fileno(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ *rval = JSVAL_TO_INT(fileno(f->fh));
+
+ return JS_TRUE;
+}
+
+/*
+ * Unlike fread(3), returns a string holding the read buffer (which could also
+ * be shorter than requested), or throws an exception on error.
+ */
+static JSBool
+file_m_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+ size_t size, nmemb, tsize, rsize;
+ JSString *str;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not an Integer");
+ return JS_FALSE;
+ }
+ size = JSVAL_TO_INT(argv[0]);
+ nmemb = JSVAL_TO_INT(argv[1]);
+ tsize = size * nmemb;
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ /*
+ * Ensure that read buffer is ready and large enough
+ */
+ if (read_charbuf_size < tsize) {
+ if (read_charbuf == NULL) {
+ if ((read_charbuf = malloc(tsize)) == NULL) {
+ QUEUE_EXCEPTION("Cannot allocate read buffer");
+ return JS_FALSE;
+ }
+ } else {
+ char *ptr;
+
+ if ((ptr = realloc(read_charbuf, tsize)) == NULL) {
+ QUEUE_EXCEPTION(
+ "Cannot reallocate read bufer");
+ return JS_FALSE;
+ }
+ read_charbuf = ptr;
+ }
+ read_charbuf_size = tsize;
+ }
+
+ if ((rsize = fread(read_charbuf, size, nmemb, f->fh)) != nmemb) {
+ if (rsize == 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+ }
+
+ if ((str = JS_NewStringCopyN(cx, read_charbuf, tsize)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+ *rval = STRING_TO_JSVAL(str);
+
+ return JS_TRUE;
+}
+
+/*
+ * Unlike fwrite(2), is simply provided the string to write rather than having
+ * to specify sizes.
+ */
+static JSBool
+file_m_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+ JSString *str;
+ char *bytes;
+ size_t size;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 1) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_STRING(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not a String");
+ return JS_FALSE;
+ }
+ str = JSVAL_TO_STRING(argv[0]);
+ if ((bytes = JS_GetStringBytes(str)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+ size = JS_GetStringLength(str);
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if (fwrite(bytes, size, 1, f->fh) != 1) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+/*
+ * Like fgets(3), is provided a maximum buffer size (which although is
+ * internal). Automatically strips the '\r', '\n' or '\r\n' if the second
+ * argument is false, or keep them otherwise. On error, we throw an
+ * exception.
+ */
+static JSBool
+file_m_gets(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+ size_t tsize;
+ JSBool nostrip;
+ JSString *str;
+ char *rstr;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_BOOLEAN(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not a Boolean");
+ return JS_FALSE;
+ }
+ tsize = JSVAL_TO_INT(argv[0]);
+ nostrip = JSVAL_TO_BOOLEAN(argv[1]);
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ /*
+ * Ensure that read buffer is ready and large enough
+ */
+ if (read_charbuf_size < tsize) {
+ if (read_charbuf == NULL) {
+ if ((read_charbuf = malloc(tsize)) == NULL) {
+ QUEUE_EXCEPTION("Cannot allocate read buffer");
+ return JS_FALSE;
+ }
+ } else {
+ char *ptr;
+
+ if ((ptr = realloc(read_charbuf, tsize)) == NULL) {
+ QUEUE_EXCEPTION(
+ "Cannot reallocate read bufer");
+ return JS_FALSE;
+ }
+ read_charbuf = ptr;
+ }
+ read_charbuf_size = tsize;
+ }
+
+ if ((rstr = fgets(read_charbuf, tsize, f->fh)) == NULL) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+ tsize = strlen(rstr);
+
+ if ((str = JS_NewStringCopyN(cx, rstr, tsize)) == NULL) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+ *rval = STRING_TO_JSVAL(str);
+
+ return JS_TRUE;
+}
+
+/*
+ * Returns an int just like fgetc(3) but throws an exception on error.
+ */
+static JSBool
+file_m_getc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+ int c;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 0) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if ((c = fgetc(f->fh)) == EOF) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+ *rval = INT_TO_JSVAL(c);
+
+ return JS_TRUE;
+}
+
+/*
+ * Like ungetc(3) but throws an exception on error.
+ */
+static JSBool
+file_m_ungetc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+ jsval *rval)
+{
+ file_t *f;
+ int c;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 1) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if ((c = ungetc(JSVAL_TO_INT(argv[0]), f->fh)) == EOF) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+ *rval = INT_TO_JSVAL(c);
+
+ return JS_TRUE;
+}
+
+/*
+ * Works with an int just like fputc(3). Throws an exception on error.
+ */
+static JSBool
+file_m_putc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+ file_t *f;
+
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 1) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(argv[0])) {
+ QUEUE_EXCEPTION("Argument 1 not an Integer");
+ return JS_FALSE;
+ }
+
+ f = JS_GetInstancePrivate(cx, obj, &file_class, NULL);
+ assert(f != NULL);
+ if (f->fh == NULL) {
+ QUEUE_EXCEPTION("File already closed");
+ return JS_FALSE;
+ }
+
+ if (fputc(JSVAL_TO_INT(argv[0]), f->fh) != 0) {
+ QUEUE_EXCEPTION(strerror(errno));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}