cfad47cfa3/tads3/vmfilobj.cpp
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 61.2 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers/*
* Copyright (c) 2001, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
vmfilobj.h - File object metaclass
Function
Implements an intrinsic class interface to operating system file I/O.
Notes
Modified
06/28/01 MJRoberts - Creation
*/
#include <assert.h>
#include "t3std.h"
#include "charmap.h"
#include "vmerr.h"
#include "vmerrnum.h"
#include "vmtype.h"
#include "vmbif.h"
#include "vmcset.h"
#include "vmstack.h"
#include "vmmeta.h"
#include "vmrun.h"
#include "vmglob.h"
#include "vmfile.h"
#include "vmobj.h"
#include "vmstr.h"
#include "vmfilobj.h"
#include "vmpredef.h"
#include "vmundo.h"
#include "vmbytarr.h"
#include "vmbignum.h"
#include "vmhost.h"
/* ------------------------------------------------------------------------ */
/*
* statics
*/
/* metaclass registration object */
static CVmMetaclassFile metaclass_reg_obj;
CVmMetaclass *CVmObjFile::metaclass_reg_ = &metaclass_reg_obj;
/* function table */
int (CVmObjFile::
*CVmObjFile::func_table_[])(VMG_ vm_obj_id_t self,
vm_val_t *retval, uint *argc) =
{
&CVmObjFile::getp_undef,
&CVmObjFile::getp_open_text,
&CVmObjFile::getp_open_data,
&CVmObjFile::getp_open_raw,
&CVmObjFile::getp_get_charset,
&CVmObjFile::getp_set_charset,
&CVmObjFile::getp_close_file,
&CVmObjFile::getp_read_file,
&CVmObjFile::getp_write_file,
&CVmObjFile::getp_read_bytes,
&CVmObjFile::getp_write_bytes,
&CVmObjFile::getp_get_pos,
&CVmObjFile::getp_set_pos,
&CVmObjFile::getp_set_pos_end,
&CVmObjFile::getp_open_res_text,
&CVmObjFile::getp_open_res_raw,
&CVmObjFile::getp_get_size
};
/*
* Vector indices - we only need to define these for the static functions,
* since we only need them to decode calls to call_stat_prop().
*/
enum vmobjfil_meta_fnset
{
VMOBJFILE_OPEN_TEXT = 1,
VMOBJFILE_OPEN_DATA = 2,
VMOBJFILE_OPEN_RAW = 3,
VMOBJFILE_OPEN_RES_TEXT = 14,
VMOBJFILE_OPEN_RES_RAW = 15
};
/* ------------------------------------------------------------------------ */
/*
* Special filename designators
*/
/* library defaults file */
#define SFID_LIB_DEFAULTS 0x0001
/* ------------------------------------------------------------------------ */
/*
* Create from stack
*/
vm_obj_id_t CVmObjFile::create_from_stack(VMG_ const uchar **pc_ptr,
uint argc)
{
/*
* we can't be created with 'new' - we can only be created via our
* static creator methods (openTextFile, openDataFile, openRawFile)
*/
err_throw(VMERR_BAD_DYNAMIC_NEW);
/* not reached, but the compiler might not know that */
AFTER_ERR_THROW(return VM_INVALID_OBJ;)
}
/* ------------------------------------------------------------------------ */
/*
* Create with no contents
*/
vm_obj_id_t CVmObjFile::create(VMG_ int in_root_set)
{
vm_obj_id_t id = vm_new_id(vmg_ in_root_set, TRUE, FALSE);
/* instantiate the object */
new (vmg_ id) CVmObjFile();
/* files are always transient */
G_obj_table->set_obj_transient(id);
/* return the new ID */
return id;
}
/*
* Create with the given character set object and file handle.
*/
vm_obj_id_t CVmObjFile::create(VMG_ int in_root_set,
vm_obj_id_t charset, osfildef *fp,
unsigned long flags, int mode, int access,
int create_readbuf,
unsigned long res_start, unsigned long res_end)
{
vm_obj_id_t id = vm_new_id(vmg_ in_root_set, TRUE, FALSE);
/* instantiate the object */
new (vmg_ id) CVmObjFile(vmg_ charset, fp, flags, mode, access,
create_readbuf, res_start, res_end);
/* files are always transient */
G_obj_table->set_obj_transient(id);
/* return the ID */
return id;
}
/* ------------------------------------------------------------------------ */
/*
* Instantiate
*/
CVmObjFile::CVmObjFile(VMG_ vm_obj_id_t charset, osfildef *fp,
unsigned long flags, int mode, int access,
int create_readbuf,
unsigned long res_start, unsigned long res_end)
{
/* allocate and initialize our extension */
ext_ = 0;
alloc_ext(vmg_ charset, fp, flags, mode, access, create_readbuf,
res_start, res_end);
}
/*
* Allocate and initialize our extension
*/
void CVmObjFile::alloc_ext(VMG_ vm_obj_id_t charset, osfildef *fp,
unsigned long flags, int mode, int access,
int create_readbuf,
unsigned long res_start, unsigned long res_end)
{
size_t siz;
/*
* if we already have an extension, delete it (and release our
* underlying system file, if any)
*/
notify_delete(vmg_ FALSE);
/*
* Figure the needed size. We need at least the standard extension;
* if we also need a read buffer, figure in space for that as well.
*/
siz = sizeof(vmobjfile_ext_t);
if (create_readbuf)
siz += sizeof(vmobjfile_readbuf_t);
/* allocate space for our extension structure */
ext_ = (char *)G_mem->get_var_heap()->alloc_mem(siz, this);
/* store the data we received from the caller */
get_ext()->fp = fp;
get_ext()->charset = charset;
get_ext()->flags = flags;
get_ext()->mode = (unsigned char)mode;
get_ext()->access = (unsigned char)access;
get_ext()->res_start = res_start;
get_ext()->res_end = res_end;
/*
* point to our read buffer, for which we allocated space contiguously
* with and immediately following our extension, if we have one
*/
if (create_readbuf)
{
/* point to our read buffer object */
get_ext()->readbuf = (vmobjfile_readbuf_t *)(get_ext() + 1);
/* initialize the read buffer with no initial data */
get_ext()->readbuf->rem = 0;
get_ext()->readbuf->ptr.set(0);
}
else
{
/* there's no read buffer at all */
get_ext()->readbuf = 0;
}
}
/* ------------------------------------------------------------------------ */
/*
* Notify of deletion
*/
void CVmObjFile::notify_delete(VMG_ int /*in_root_set*/)
{
/* if we have an extension, clean it up */
if (ext_ != 0)
{
/* close our file if we have one */
if (get_ext()->fp != 0)
osfcls(get_ext()->fp);
/* free our extension */
G_mem->get_var_heap()->free_mem(ext_);
}
}
/* ------------------------------------------------------------------------ */
/*
* set a property
*/
void CVmObjFile::set_prop(VMG_ class CVmUndo *,
vm_obj_id_t, vm_prop_id_t,
const vm_val_t *)
{
err_throw(VMERR_INVALID_SETPROP);
}
/* ------------------------------------------------------------------------ */
/*
* get a property
*/
int CVmObjFile::get_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
vm_obj_id_t self, vm_obj_id_t *source_obj,
uint *argc)
{
uint func_idx;
/* translate the property index to an index into our function table */
func_idx = G_meta_table
->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop);
/* call the appropriate function */
if ((this->*func_table_[func_idx])(vmg_ self, retval, argc))
{
*source_obj = metaclass_reg_->get_class_obj(vmg0_);
return TRUE;
}
/* inherit default handling */
return CVmObject::get_prop(vmg_ prop, retval, self, source_obj, argc);
}
/*
* call a static property
*/
int CVmObjFile::call_stat_prop(VMG_ vm_val_t *result,
const uchar **pc_ptr, uint *argc,
vm_prop_id_t prop)
{
/* translate the property into a function vector index */
switch(G_meta_table
->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop))
{
case VMOBJFILE_OPEN_TEXT:
return s_getp_open_text(vmg_ result, argc, FALSE);
case VMOBJFILE_OPEN_DATA:
return s_getp_open_data(vmg_ result, argc);
case VMOBJFILE_OPEN_RAW:
return s_getp_open_raw(vmg_ result, argc, FALSE);
case VMOBJFILE_OPEN_RES_TEXT:
return s_getp_open_text(vmg_ result, argc, TRUE);
case VMOBJFILE_OPEN_RES_RAW:
return s_getp_open_raw(vmg_ result, argc, TRUE);
default:
/* it's not one of ours - inherit from the base object metaclass */
return CVmObject::call_stat_prop(vmg_ result, pc_ptr, argc, prop);
}
}
/* ------------------------------------------------------------------------ */
/*
* load from an image file
*/
void CVmObjFile::load_from_image(VMG_ vm_obj_id_t self,
const char *ptr, size_t siz)
{
/* load from the image data */
load_image_data(vmg_ ptr, siz);
/*
* save our image data pointer in the object table, so that we can
* access it (without storing it ourselves) during a reload
*/
G_obj_table->save_image_pointer(self, ptr, siz);
}
/*
* reload the object from image data
*/
void CVmObjFile::reload_from_image(VMG_ vm_obj_id_t /*self*/,
const char *ptr, size_t siz)
{
/* load the image data */
load_image_data(vmg_ ptr, siz);
}
/*
* load or re-load image data
*/
void CVmObjFile::load_image_data(VMG_ const char *ptr, size_t siz)
{
vm_obj_id_t charset;
unsigned long flags;
int mode;
int access;
/* read our character set */
charset = vmb_get_objid(ptr);
ptr += VMB_OBJECT_ID;
/* get the mode and access values */
mode = (unsigned char)*ptr++;
access = (unsigned char)*ptr++;
/* get the flags */
flags = t3rp4u(ptr);
/*
* add in the out-of-sync flag, since we've restored the state from a
* past state and thus we're out of sync with the external file system
* environment
*/
flags |= VMOBJFILE_OUT_OF_SYNC;
/*
* Initialize our extension - we have no underlying native file
* handle, since the file is out of sync by virtue of being loaded
* from a previously saved image state. Note that we don't need a
* read buffer because the file is inherently out of sync and thus
* cannot be read.
*/
alloc_ext(vmg_ charset, 0, flags, mode, access, FALSE, 0, 0);
}
/* ------------------------------------------------------------------------ */
/*
* save to a file
*/
void CVmObjFile::save_to_file(VMG_ class CVmFile *fp)
{
/* files are always transient, so should never be saved */
assert(FALSE);
}
/*
* restore from a file
*/
void CVmObjFile::restore_from_file(VMG_ vm_obj_id_t self,
CVmFile *fp, CVmObjFixup *)
{
/* files are always transient, so should never be savd */
}
/* ------------------------------------------------------------------------ */
/*
* Mark as referenced all of the objects to which we refer
*/
void CVmObjFile::mark_refs(VMG_ uint state)
{
/* mark our character set object, if we have one */
if (get_ext()->charset != VM_INVALID_OBJ)
G_obj_table->mark_all_refs(get_ext()->charset, state);
}
/* ------------------------------------------------------------------------ */
/*
* Note that we're seeking within the file.
*/
void CVmObjFile::note_file_seek(VMG_ vm_obj_id_t self, int is_explicit)
{
/*
* if it's an explicit seek, invalidate our internal read buffer and
* note that the stdio buffers have been invalidated
*/
if (is_explicit)
{
/* the read buffer (if we have one) is invalid after a seek */
if (get_ext()->readbuf != 0)
get_ext()->readbuf->rem = 0;
/*
* mark the last operation as clearing stdio buffering - when we
* explicitly seek, stdio automatically invalidates its internal
* buffers
*/
get_ext()->flags &= ~VMOBJFILE_STDIO_BUF_DIRTY;
}
}
/*
* Update stdio buffering for a switch between read and write mode, if
* necessary. Any time we perform consecutive read and write operations,
* stdio requires us to explicitly seek when performing consecutive
* dissimilar operations. In other words, if we read then write, we must
* seek before the write, and likewise if we write then read.
*/
void CVmObjFile::switch_read_write_mode(int writing)
{
/* if we're writing, invalidate the read buffer */
if (writing && get_ext()->readbuf != 0)
get_ext()->readbuf->rem = 0;
/*
* if we just performed a read or write operation, we must seek if
* we're performing the opposite type of operation now
*/
if ((get_ext()->flags & VMOBJFILE_STDIO_BUF_DIRTY) != 0)
{
int was_writing;
/* check what type of operation we did last */
was_writing = ((get_ext()->flags & VMOBJFILE_LAST_OP_WRITE) != 0);
/*
* if we're switching operations, explicitly seek to the current
* location to flush the stdio buffers
*/
if ((writing && !was_writing) || (!writing && was_writing))
osfseek(get_ext()->fp, osfpos(get_ext()->fp), OSFSK_SET);
}
/*
* remember that this operation is stdio-buffered, so that we'll know
* we need to seek if we perform the opposite type of application
* after this one
*/
get_ext()->flags |= VMOBJFILE_STDIO_BUF_DIRTY;
/* remember which type of operation we're performing */
if (writing)
get_ext()->flags |= VMOBJFILE_LAST_OP_WRITE;
else
get_ext()->flags &= ~VMOBJFILE_LAST_OP_WRITE;
}
/* ------------------------------------------------------------------------ */
/*
* Retrieve the filename and access mode arguments.
*/
void CVmObjFile::get_filename_and_access(VMG_ char *fname, size_t fname_siz,
int *access, int is_resource_file)
{
int is_special_file = FALSE;
/*
* check to see if we have an explicit filename string, or an integer
* giving a special system file ID
*/
if (G_stk->get(0)->typ == VM_INT)
{
char path[OSFNMAX];
/* start with no path, in case we have trouble retrieving it */
path[0] = '\0';
/* we have an integer, which is a special file designator */
switch (CVmBif::pop_int_val(vmg0_))
{
case SFID_LIB_DEFAULTS:
/* get the system application data path */
G_host_ifc->get_special_file_path(path, sizeof(path),
OS_GSP_T3_APP_DATA);
/* add the filename */
os_build_full_path(fname, fname_siz, path, "settings.txt");
break;
default:
/* invalid filename value */
err_throw(VMERR_BAD_VAL_BIF);
}
/* note that we have a special file, for file safety purposes */
is_special_file = TRUE;
}
else
{
/* we must have an explicit filename string - pop it */
CVmBif::pop_str_val_fname(vmg_ fname, fname_siz);
}
/*
* retrieve the access mode; if it's a resource file, the mode is
* implicitly 'read'
*/
if (is_resource_file)
*access = VMOBJFILE_ACCESS_READ;
else
*access = CVmBif::pop_int_val(vmg0_);
/*
* If this isn't a special file, then check the file safety mode to
* ensure this operation is allowed. Reading resources is always
* allowed, regardless of the safety mode, since resources are
* read-only and are inherently constrained in the paths they can
* access. Likewise, special files bypass the safety settings, because
* the interpreter controls the names and locations of these files,
* ensuring that they're inherently safe.
*/
if (!is_resource_file && !is_special_file)
check_safety_for_open(vmg_ fname, *access);
}
/* ------------------------------------------------------------------------ */
/*
* Static property evaluator - open a text file
*/
int CVmObjFile::s_getp_open_text(VMG_ vm_val_t *retval, uint *in_argc,
int is_resource_file)
{
uint argc = (in_argc != 0 ? *in_argc : 0);
static CVmNativeCodeDesc desc_file(2, 1);
static CVmNativeCodeDesc desc_res(1, 1);
char fname[OSFNMAX];
int access;
vm_obj_id_t cset_obj;
osfildef *fp;
int create_readbuf;
unsigned long res_start;
unsigned long res_end;
unsigned int flags;
/* check arguments */
if (get_prop_check_argc(retval, in_argc,
is_resource_file ? &desc_res : &desc_file))
return TRUE;
/* initialize the flags to indicate a text-mode file */
flags = 0;
/* add the resource-file flag if appropriate */
if (is_resource_file)
flags |= VMOBJFILE_IS_RESOURCE;
/* presume we can use the entire file */
res_start = 0;
res_end = 0;
/* retrieve the filename */
get_filename_and_access(vmg_ fname, sizeof(fname),
&access, is_resource_file);
/* presume we won't need a read buffer */
create_readbuf = FALSE;
/* if there's a character set name or object, retrieve it */
if (argc > 2)
{
/*
* check to see if it's a CharacterSet object; if it's not, it
* must be a string giving the character set name
*/
if (G_stk->get(0)->typ == VM_OBJ
&& CVmObjCharSet::is_charset(vmg_ G_stk->get(0)->val.obj))
{
/* retrieve the CharacterSet reference */
cset_obj = CVmBif::pop_obj_val(vmg0_);
}
else
{
const char *str;
size_t len;
/* it's not a CharacterSet, so it must be a character set name */
str = G_stk->get(0)->get_as_string(vmg0_);
if (str == 0)
err_throw(VMERR_BAD_TYPE_BIF);
/* get the length and skip the length prefix */
len = vmb_get_len(str);
str += VMB_LEN;
/* create a mapper for the given name */
cset_obj = CVmObjCharSet::create(vmg_ FALSE, str, len);
}
}
else
{
/* no character set is specified - use US-ASCII by default */
cset_obj = CVmObjCharSet::create(vmg_ FALSE, "us-ascii", 8);
}
/* push the character map object onto the stack for gc protection */
G_stk->push()->set_obj(cset_obj);
/* open the file for reading or writing, as appropriate */
switch(access)
{
case VMOBJFILE_ACCESS_READ:
/* open a resource file or file system file, as appropriate */
if (is_resource_file)
{
unsigned long res_len;
/* it's a resource - open it */
fp = G_host_ifc->find_resource(fname, strlen(fname), &res_len);
/*
* if we found the resource, note the start and end seek
* positions, so we can limit reading of the underlying file
* to the section that contains the resource data
*/
if (fp != 0)
{
/* the file is initially at the start of the resource data */
res_start = osfpos(fp);
/* note the offset of the first byte after the resource */
res_end = res_start + res_len;
}
}
else
{
/*
* Not a resource - open an ordinary text file for reading.
* Even though we're going to treat the file as a text file,
* open it in binary mode, since we'll do our own universal
* newline translations; this allows us to work with files in
* any character set, and using almost any newline
* conventions, so files copied from other systems will be
* fully usable even if they haven't been fixed up to local
* conventions.
*/
fp = osfoprb(fname, OSFTTEXT);
}
/* make sure we opened it successfully */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_not_found_exc,
0, "file not found");
/* we need a read buffer */
create_readbuf = TRUE;
break;
case VMOBJFILE_ACCESS_WRITE:
/* open for writing */
fp = osfopwb(fname, OSFTTEXT);
/* make sure we created it successfully */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_creation_exc,
0, "error creating file");
break;
case VMOBJFILE_ACCESS_RW_KEEP:
/* open for read/write, keeping existing contents */
fp = osfoprwb(fname, OSFTTEXT);
/* make sure we were able to find or create the file */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
0, "error opening file");
break;
case VMOBJFILE_ACCESS_RW_TRUNC:
/* open for read/write, truncating existing contents */
fp = osfoprwtb(fname, OSFTTEXT);
/* make sure we were successful */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
0, "error opening file");
break;
default:
fp = 0;
err_throw(VMERR_BAD_VAL_BIF);
}
/* create our file object */
retval->set_obj(create(vmg_ FALSE, cset_obj, fp, flags,
VMOBJFILE_MODE_TEXT, access, create_readbuf,
res_start, res_end));
/* discard gc protection */
G_stk->discard();
/* handled */
return TRUE;
}
/*
* Static property evaluator - open a data file
*/
int CVmObjFile::s_getp_open_data(VMG_ vm_val_t *retval, uint *argc)
{
/* use the generic binary file opener in 'data' mode */
return open_binary(vmg_ retval, argc, VMOBJFILE_MODE_DATA, FALSE);
}
/*
* Static property evaluator - open a raw file
*/
int CVmObjFile::s_getp_open_raw(VMG_ vm_val_t *retval, uint *argc,
int is_resource_file)
{
/* use the generic binary file opener in 'raw' mode */
return open_binary(vmg_ retval, argc, VMOBJFILE_MODE_RAW,
is_resource_file);
}
/*
* Generic binary file opener - common to 'data' and 'raw' files
*/
int CVmObjFile::open_binary(VMG_ vm_val_t *retval, uint *argc, int mode,
int is_resource_file)
{
static CVmNativeCodeDesc file_desc(2);
static CVmNativeCodeDesc res_desc(1);
char fname[OSFNMAX];
int access;
osfildef *fp;
unsigned long res_start;
unsigned long res_end;
unsigned int flags;
/* check arguments */
if (get_prop_check_argc(retval, argc,
is_resource_file ? &res_desc : &file_desc))
return TRUE;
/* initialize the flags */
flags = 0;
/* set the resource-file flag, if appropriate */
if (is_resource_file)
flags |= VMOBJFILE_IS_RESOURCE;
/* presume we can use the entire file */
res_start = 0;
res_end = 0;
/* retrieve the filename and access mode */
get_filename_and_access(vmg_ fname, sizeof(fname),
&access, is_resource_file);
/* open the file in binary mode, with the desired access type */
switch(access)
{
case VMOBJFILE_ACCESS_READ:
/* open the resource or ordinary file, as appropriate */
if (is_resource_file)
{
unsigned long res_len;
/* it's a resource - open it */
fp = G_host_ifc->find_resource(fname, strlen(fname), &res_len);
/*
* if we found the resource, note the start and end seek
* positions, so we can limit reading of the underlying file
* to the section that contains the resource data
*/
if (fp != 0)
{
/* the file is initially at the start of the resource data */
res_start = osfpos(fp);
/* note the offset of the first byte after the resource */
res_end = res_start + res_len;
}
}
else
{
/* open the ordinary file in binary mode for reading only */
fp = osfoprb(fname, OSFTBIN);
}
/* make sure we were able to find it and open it */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_not_found_exc,
0, "file not found");
break;
case VMOBJFILE_ACCESS_WRITE:
/* open in binary mode for writing only */
fp = osfopwb(fname, OSFTBIN);
/* make sure we were able to create the file successfully */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_creation_exc,
0, "error creating file");
break;
case VMOBJFILE_ACCESS_RW_KEEP:
/* open for read/write, keeping existing contents */
fp = osfoprwb(fname, OSFTBIN);
/* make sure we were able to find or create the file */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
0, "error opening file");
break;
case VMOBJFILE_ACCESS_RW_TRUNC:
/* open for read/write, truncating existing contents */
fp = osfoprwtb(fname, OSFTBIN);
/* make sure we were successful */
if (fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
0, "error opening file");
break;
default:
fp = 0;
err_throw(VMERR_BAD_VAL_BIF);
}
/* create our file object */
retval->set_obj(create(vmg_ FALSE, VM_INVALID_OBJ, fp,
flags, mode, access, FALSE, res_start, res_end));
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Check the safety settings to determine if an open operation is allowed.
* If the access is not allowed, we'll throw an error.
*/
void CVmObjFile::check_safety_for_open(VMG_ const char *fname, int access)
{
int safety;
int in_same_dir;
/* get the current file safety level from the host application */
safety = G_host_ifc->get_io_safety();
/*
* Check to see if the file is in the current directory - if not, we
* may have to disallow the operation based on safety level settings.
* If the file has any sort of directory prefix, assume it's not in
* the same directory; if not, it must be. This is actually overly
* conservative, since the path may be a relative path or even an
* absolute path that points to the current directory, but the
* important thing is whether we're allowing files to specify paths at
* all.
*/
in_same_dir = (os_get_root_name((char *)fname) == fname);
/* check for conformance with the safety level setting */
switch (access)
{
case VMOBJFILE_ACCESS_READ:
/*
* we want only read access - we can't read at all if the safety
* level isn't READ_CUR or below, and we must be at level
* READ_ANY_WRITE_CUR or lower to read from a file not in the
* current directory
*/
if (safety > VM_IO_SAFETY_READ_CUR
|| (!in_same_dir && safety > VM_IO_SAFETY_READ_ANY_WRITE_CUR))
{
/* this operation is not allowed - throw an error */
G_interpreter->throw_new_class(vmg_ G_predef->file_safety_exc,
0, "prohibited file access");
}
break;
case VMOBJFILE_ACCESS_WRITE:
case VMOBJFILE_ACCESS_RW_KEEP:
case VMOBJFILE_ACCESS_RW_TRUNC:
/*
* writing - we must be safety level of at least READWRITE_CUR to
* write at all, and we must be at level MINIMUM to write a file
* that's not in the current directory
*/
if (safety > VM_IO_SAFETY_READWRITE_CUR
|| (!in_same_dir && safety > VM_IO_SAFETY_MINIMUM))
{
/* this operation is not allowed - throw an error */
G_interpreter->throw_new_class(vmg_ G_predef->file_safety_exc,
0, "prohibited file access");
}
break;
}
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - get the character set
*/
int CVmObjFile::getp_get_charset(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* if we have a character set object, return it; otherwise return nil */
if (get_ext()->charset != VM_INVALID_OBJ)
retval->set_obj(get_ext()->charset);
else
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - set the character set
*/
int CVmObjFile::getp_set_charset(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(1);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure it's really a character set object */
if (G_stk->get(0)->typ != VM_NIL
&& (G_stk->get(0)->typ != VM_OBJ
|| !CVmObjCharSet::is_charset(vmg_ G_stk->get(0)->val.obj)))
err_throw(VMERR_BAD_TYPE_BIF);
/* remember the new character set */
if (G_stk->get(0)->typ == VM_NIL)
get_ext()->charset = VM_INVALID_OBJ;
else
get_ext()->charset = G_stk->get(0)->val.obj;
/* discard the argument */
G_stk->discard();
/* no return value */
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Check that we're in a valid state to perform file operations.
*/
void CVmObjFile::check_valid_file(VMG0_)
{
/* if we're 'out of sync', we can't perform any operations on it */
if ((get_ext()->flags & VMOBJFILE_OUT_OF_SYNC) != 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_sync_exc,
0, "file out of sync");
/* if the file has been closed, we can't perform any operations on it */
if (get_ext()->fp == 0)
G_interpreter->throw_new_class(vmg_ G_predef->file_closed_exc, 0,
"operation attempted on closed file");
}
/*
* Check that we have read access
*/
void CVmObjFile::check_read_access(VMG0_)
{
/* we have read access unless we're in write-only mode */
if (get_ext()->access == VMOBJFILE_ACCESS_WRITE)
G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc, 0,
"wrong file mode");
}
/*
* Check that we have write access
*/
void CVmObjFile::check_write_access(VMG0_)
{
/* we have write access unless we're in read-only mode */
if (get_ext()->access == VMOBJFILE_ACCESS_READ)
G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc, 0,
"wrong file mode");
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - close the file
*/
int CVmObjFile::getp_close_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* close the underlying system file */
osfcls(get_ext()->fp);
/* forget the underlying system file, since it's no longer valid */
get_ext()->fp = 0;
/* no return value */
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - read from the file
*/
int CVmObjFile::getp_read_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* push a self-reference for gc protection */
G_stk->push()->set_obj(self);
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* check that we have read access */
check_read_access(vmg0_);
/* note the implicit seeking */
note_file_seek(vmg_ self, FALSE);
/* flush stdio buffers as needed and note the read operation */
switch_read_write_mode(FALSE);
/* read according to our mode */
switch(get_ext()->mode)
{
case VMOBJFILE_MODE_TEXT:
/* read a line of text */
read_text_mode(vmg_ retval);
break;
case VMOBJFILE_MODE_DATA:
/* read in data mode */
read_data_mode(vmg_ retval);
break;
case VMOBJFILE_MODE_RAW:
/* can't use this call on this type of file */
G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
0, "wrong file mode");
break;
}
/* discard the gc protection */
G_stk->discard();
/* handled */
return TRUE;
}
/*
* Read a value in text mode
*/
void CVmObjFile::read_text_mode(VMG_ vm_val_t *retval)
{
CVmObjString *str;
size_t str_len;
osfildef *fp = get_ext()->fp;
vmobjfile_readbuf_t *readbuf = get_ext()->readbuf;
CCharmapToUni *charmap;
int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
/* we haven't yet constructed a string */
str = 0;
str_len = 0;
/* get our character mapper */
charmap = ((CVmObjCharSet *)vm_objp(vmg_ get_ext()->charset))
->get_to_uni(vmg0_);
/* assume we'll fail to read anything, in which case we'll return nil */
retval->set_nil();
/*
* push the nil value - we'll always keep our intermediate value on
* the stack so that the garbage collector will know it's referenced
*/
G_stk->push(retval);
/*
* Read a line of text from the file into our buffer. Keep going
* until we read an entire line; we might have to read the line in
* chunks, since the line might end up being longer than our buffer.
*/
for (;;)
{
wchar_t found_nl;
char *start;
size_t new_len;
size_t nl_len;
/* replenish the read buffer if it's empty */
if (readbuf->rem == 0
&& !readbuf->refill(charmap, fp, is_res_file, get_ext()->res_end))
break;
/* note where we started this chunk */
start = readbuf->ptr.getptr();
/* scan for and remove any trailing newline */
for (found_nl = '\0' ; readbuf->rem != 0 ;
readbuf->ptr.inc(&readbuf->rem))
{
wchar_t cur;
/* get the current character */
cur = readbuf->ptr.getch();
/*
* check for a newline (note that 0x2028 is the unicode line
* separator character)
*/
if (cur == '\n' || cur == '\r' || cur == 0x2028)
{
/* note the newline */
found_nl = cur;
/* no need to look any further */
break;
}
}
/* note the length of the current segment */
new_len = readbuf->ptr.getptr() - start;
/*
* if there's a newline character, include an extra byte for the
* '\n' we'll include in the result
*/
nl_len = (found_nl != '\0');
/*
* If this is our first segment, construct a new string from this
* chunk; otherwise, add to the existing string.
*
* Note that in either case, if we found a newline in the buffer,
* we will NOT add the actual newline we found to the result
* string. Rather, we'll add a '\n' character to the result
* string, no matter what kind of newline we found. This ensures
* that the data read uses a consistent format, regardless of the
* local system convention where the file was created.
*/
if (str == 0)
{
/* create our first segment's string */
retval->set_obj(CVmObjString::
create(vmg_ FALSE, new_len + nl_len));
str = (CVmObjString *)vm_objp(vmg_ retval->val.obj);
/* copy the segment into the string object */
memcpy(str->cons_get_buf(), start, new_len);
/* add a '\n' if we found a newline */
if (found_nl != '\0')
*(str->cons_get_buf() + new_len) = '\n';
/* this is the length of the string so far */
str_len = new_len + nl_len;
/*
* replace the stack placeholder with our string, so the
* garbage collector will know it's still in use
*/
G_stk->discard();
G_stk->push(retval);
}
else
{
CVmObjString *new_str;
/*
* create a new string to hold the contents of the old string
* plus the new buffer
*/
retval->set_obj(CVmObjString::create(vmg_ FALSE,
str_len + new_len + nl_len));
new_str = (CVmObjString *)vm_objp(vmg_ retval->val.obj);
/* copy the old string into the new string */
memcpy(new_str->cons_get_buf(),
str->get_as_string(vmg0_) + VMB_LEN, str_len);
/* add the new chunk after the copy of the old string */
memcpy(new_str->cons_get_buf() + str_len, start, new_len);
/* add the newline if necessary */
if (found_nl != '\0')
*(new_str->cons_get_buf() + str_len + new_len) = '\n';
/* the new string now replaces the old string */
str = new_str;
str_len += new_len + nl_len;
/*
* replace our old intermediate value on the stack with the
* new string - the old string isn't needed any more, so we
* can leave it unreferenced, but we are still using the new
* string
*/
G_stk->discard();
G_stk->push(retval);
}
/* if we found a newline in this segment, we're done */
if (found_nl != '\0')
{
/* skip the newline in the input */
readbuf->ptr.inc(&readbuf->rem);
/* replenish the read buffer if it's empty */
if (readbuf->rem == 0)
readbuf->refill(charmap, fp, is_res_file, get_ext()->res_end);
/*
* check for a complementary newline character, for systems
* that use \n\r or \r\n pairs
*/
if (readbuf->rem != 0)
{
wchar_t nxt;
/* get the next character */
nxt = readbuf->ptr.getch();
/* check for a complementary character */
if ((found_nl == '\n' && nxt == '\r')
|| (found_nl == '\r' && nxt == '\n'))
{
/*
* we have a pair sequence - skip the second character
* of the sequence
*/
readbuf->ptr.inc(&readbuf->rem);
}
}
/* we've found the newline, so we're done with the string */
break;
}
}
/*
* we now can discard the string we've been keeping on the stack to
* for garbage collection protection
*/
G_stk->discard();
}
/*
* Read a value in 'data' mode
*/
void CVmObjFile::read_data_mode(VMG_ vm_val_t *retval)
{
char buf[32];
CVmObjString *str_obj;
vm_obj_id_t str_id;
osfildef *fp = get_ext()->fp;
/* read the type flag */
if (osfrb(fp, buf, 1))
{
/* end of file - return nil */
retval->set_nil();
return;
}
/* see what we have */
switch((vm_datatype_t)buf[0])
{
case VMOBJFILE_TAG_INT:
/* read the INT4 value */
if (osfrb(fp, buf, 4))
goto io_error;
/* set the integer value from the buffer */
retval->set_int(osrp4(buf));
break;
case VMOBJFILE_TAG_ENUM:
/* read the UINT4 value */
if (osfrb(fp, buf, 4))
goto io_error;
/* set the 'enum' value */
retval->set_enum(t3rp4u(buf));
break;
case VMOBJFILE_TAG_STRING:
/*
* read the string's length - note that this length is two
* higher than the actual length of the string, because it
* includes the length prefix bytes
*/
if (osfrb(fp, buf, 2))
goto io_error;
/*
* allocate a new string of the required size (deducting two
* bytes from the indicated size, since the string allocator
* only wants to know about the bytes of the string we want to
* store, not the length prefix part)
*/
str_id = CVmObjString::create(vmg_ FALSE, osrp2(buf) - 2);
str_obj = (CVmObjString *)vm_objp(vmg_ str_id);
/* read the bytes of the string into the object's buffer */
if (osfrb(fp, str_obj->cons_get_buf(), osrp2(buf) - 2))
goto io_error;
/* success - set the string return value, and we're done */
retval->set_obj(str_id);
break;
case VMOBJFILE_TAG_TRUE:
/* it's a simple 'true' value */
retval->set_true();
break;
case VMOBJFILE_TAG_BIGNUM:
/* read the BigNumber value and return a new BigNumber object */
if (CVmObjBigNum::read_from_data_file(vmg_ retval, fp))
goto io_error;
break;
case VMOBJFILE_TAG_BYTEARRAY:
/* read the ByteArray value and return a new ByteArray object */
if (CVmObjByteArray::read_from_data_file(vmg_ retval, fp))
goto io_error;
break;
default:
/* invalid data - throw an error */
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* done */
return;
io_error:
/*
* we'll come here if we read the type tag correctly but encounter
* an I/O error reading the value - this indicates a corrupted input
* stream, so throw an I/O error
*/
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - write to the file
*/
int CVmObjFile::getp_write_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(1);
const vm_val_t *argval;
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/*
* get a pointer to the argument value, but leave it on the stack
* for now to protect against losing it in garbage collection
*/
argval = G_stk->get(0);
/* push a self-reference for gc protection */
G_stk->push()->set_obj(self);
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* check that we have write access */
check_write_access(vmg0_);
/* deal with stdio buffering if we're changing modes */
switch_read_write_mode(TRUE);
/* read according to our mode */
switch(get_ext()->mode)
{
case VMOBJFILE_MODE_TEXT:
/* read a line of text */
write_text_mode(vmg_ argval);
break;
case VMOBJFILE_MODE_DATA:
/* read in data mode */
write_data_mode(vmg_ argval);
break;
case VMOBJFILE_MODE_RAW:
/* can't use this call on this type of file */
G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
0, "wrong file mode");
break;
}
/* discard our gc protection and argument */
G_stk->discard(2);
/* no return value - return nil by default */
retval->set_nil();
/* handled */
return TRUE;
}
/*
* Write a value in text mode
*/
void CVmObjFile::write_text_mode(VMG_ const vm_val_t *val)
{
char conv_buf[128];
vm_val_t new_str;
CCharmapToLocal *charmap;
const char *constp;
const char *p, *startp;
size_t rem;
/* get our character mapper */
charmap = ((CVmObjCharSet *)vm_objp(vmg_ get_ext()->charset))
->get_to_local(vmg0_);
/* convert the value to a string */
constp = CVmObjString::cvt_to_str(vmg_ &new_str,
conv_buf, sizeof(conv_buf),
val, 10);
/* scan for newlines - we need to write newline sequences specially */
for (startp = constp + VMB_LEN, rem = vmb_get_len(constp) ;
rem != 0 ; )
{
/* scan to the next newline */
for (p = startp ; rem != 0 && *p != '\n' ; ++p, --rem) ;
/* write this chunk through the character mapper */
if (p != startp
&& charmap->write_file(get_ext()->fp, startp, p - startp))
{
/* the write failed - throw an I/O exception */
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* write the newline, if applicable */
if (rem != 0
&& charmap->write_file(get_ext()->fp, OS_NEWLINE_SEQ,
strlen(OS_NEWLINE_SEQ)))
{
/* the write failed - throw an I/O exception */
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* if we're at a newline, skip it */
if (rem != 0)
++p, --rem;
}
}
/*
* Write a value in 'data' mode
*/
void CVmObjFile::write_data_mode(VMG_ const vm_val_t *val)
{
char buf[32];
osfildef *fp = get_ext()->fp;
vm_val_t new_str;
const char *constp;
/* see what type of data we want to put */
switch(val->typ)
{
case VM_INT:
/* put the type in the buffer */
buf[0] = VMOBJFILE_TAG_INT;
/* add the value in INT4 format */
oswp4(buf + 1, val->val.intval);
/* write out the type prefix plus the value */
if (osfwb(fp, buf, 5))
goto io_error;
/* done */
break;
case VM_ENUM:
/* put the type in the buffer */
buf[0] = VMOBJFILE_TAG_ENUM;
/* add the value in INT4 format */
oswp4(buf + 1, val->val.enumval);
/* write out the type prefix plus the value */
if (osfwb(fp, buf, 5))
goto io_error;
/* done */
break;
case VM_SSTRING:
/* get the string value pointer */
constp = val->get_as_string(vmg0_);
write_binary_string:
/* write the type prefix byte */
buf[0] = VMOBJFILE_TAG_STRING;
if (osfwb(fp, buf, 1))
goto io_error;
/*
* write the length prefix - for TADS 2 compatibility, include
* the bytes of the prefix itself in the length count
*/
oswp2(buf, vmb_get_len(constp) + 2);
if (osfwb(fp, buf, 2))
goto io_error;
/* write the string's bytes */
if (osfwb(fp, constp + VMB_LEN, vmb_get_len(constp)))
goto io_error;
/* done */
break;
case VM_OBJ:
/*
* Write BigNumber and ByteArray types in special formats. For
* other types, try converting to a string.
*/
if (CVmObjBigNum::is_bignum_obj(vmg_ val->val.obj))
{
CVmObjBigNum *bignum;
/* we know it's a BigNumber - cast it properly */
bignum = (CVmObjBigNum *)vm_objp(vmg_ val->val.obj);
/* write the type tag */
buf[0] = VMOBJFILE_TAG_BIGNUM;
if (osfwb(fp, buf, 1))
goto io_error;
/* write it out */
if (bignum->write_to_data_file(fp))
goto io_error;
}
else if (CVmObjByteArray::is_byte_array(vmg_ val->val.obj))
{
CVmObjByteArray *bytarr;
/* we know it's a ByteArray - cast it properly */
bytarr = (CVmObjByteArray *)vm_objp(vmg_ val->val.obj);
/* write the type tag */
buf[0] = VMOBJFILE_TAG_BYTEARRAY;
if (osfwb(fp, buf, 1))
goto io_error;
/* write the array */
if (bytarr->write_to_data_file(fp))
goto io_error;
}
else
{
/*
* Cast it to a string value and write that out. Note that
* we can ignore garbage collection for any new string we've
* created, since we're just calling the OS-level file
* writer, which will never invoke garbage collection.
*/
constp = vm_objp(vmg_ val->val.obj)
->cast_to_string(vmg_ val->val.obj, &new_str);
goto write_binary_string;
}
break;
case VM_TRUE:
/*
* All we need for this is the type tag. Note that we can't
* write nil because we'd have no way of reading it back in - a
* nil return from file_read indicates that we've reached the
* end of the file. So there's no point in writing nil to a
* file.
*/
buf[0] = VMOBJFILE_TAG_TRUE;
if (osfwb(fp, buf, 1))
goto io_error;
/* done */
break;
default:
/* other types are not acceptable */
err_throw(VMERR_BAD_TYPE_BIF);
}
/* done */
return;
io_error:
/* the write failed - throw an i/o exception */
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - read raw bytes from the file
*/
int CVmObjFile::getp_read_bytes(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *in_argc)
{
static CVmNativeCodeDesc desc(1, 2);
uint argc = (in_argc != 0 ? *in_argc : 0);
vm_val_t arr_val;
CVmObjByteArray *arr;
unsigned long idx;
unsigned long len;
int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
/* check arguments */
if (get_prop_check_argc(retval, in_argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* check that we have read access */
check_read_access(vmg0_);
/* we can only use this call on 'raw' files */
if (get_ext()->mode != VMOBJFILE_MODE_RAW)
G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
0, "wrong file mode");
/* retrieve the ByteArray destination */
G_stk->pop(&arr_val);
/* make sure it's really a ByteArray object */
if (arr_val.typ != VM_OBJ
|| !CVmObjByteArray::is_byte_array(vmg_ arr_val.val.obj))
err_throw(VMERR_BAD_TYPE_BIF);
/* we know it's a byte array object, so cast it */
arr = (CVmObjByteArray *)vm_objp(vmg_ arr_val.val.obj);
/* presume we'll try to fill the entire array */
idx = 1;
len = arr->get_element_count();
/* if we have a starting index argument, retrieve it */
if (argc >= 2)
idx = (unsigned long)CVmBif::pop_int_val(vmg0_);
/* if we have a length argument, retrieve it */
if (argc >= 3)
len = (unsigned long)CVmBif::pop_int_val(vmg0_);
/* push a self-reference for gc protection */
G_stk->push()->set_obj(self);
/* note the implicit seeking */
note_file_seek(vmg_ self, FALSE);
/* deal with stdio buffering issues if necessary */
switch_read_write_mode(FALSE);
/*
* limit the reading to the remaining data in the file, if it's a
* resource file
*/
if (is_res_file)
{
unsigned long cur_seek_pos;
/* check to see where we are relative to the end of the resource */
cur_seek_pos = osfpos(get_ext()->fp);
if (cur_seek_pos >= get_ext()->res_end)
{
/* we're already past the end - there's nothing left */
len = 0;
}
else
{
unsigned long limit;
/* calculate the limit */
limit = get_ext()->res_end - cur_seek_pos;
/* apply the limit if the request exceeds it */
if (len > limit)
len = limit;
}
}
/*
* read the data into the array, and return the number of bytes we
* actually manage to read
*/
retval->set_int(arr->read_from_file(get_ext()->fp, idx, len));
/* discard our gc protection */
G_stk->discard();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - write raw bytes to the file
*/
int CVmObjFile::getp_write_bytes(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *in_argc)
{
static CVmNativeCodeDesc desc(1, 2);
uint argc = (in_argc != 0 ? *in_argc : 0);
vm_val_t arr_val;
CVmObjByteArray *arr;
unsigned long idx;
unsigned long len;
/* check arguments */
if (get_prop_check_argc(retval, in_argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* check that we have write access */
check_write_access(vmg0_);
/* make sure the byte array argument is really a byte array */
G_stk->pop(&arr_val);
if (arr_val.typ != VM_OBJ
|| !CVmObjByteArray::is_byte_array(vmg_ arr_val.val.obj))
err_throw(VMERR_BAD_TYPE_BIF);
/* we know it's a byte array, so we can simply cast it */
arr = (CVmObjByteArray *)vm_objp(vmg_ arr_val.val.obj);
/* assume we'll write the entire byte array */
idx = 1;
len = arr->get_element_count();
/* if we have a starting index, retrieve it */
if (argc >= 2)
idx = (unsigned long)CVmBif::pop_int_val(vmg0_);
/* if we have a length, retrieve it */
if (argc >= 3)
len = (unsigned long)CVmBif::pop_int_val(vmg0_);
/* push a self-reference for gc protection */
G_stk->push()->set_obj(self);
/* flush stdio buffers as needed and note the read operation */
switch_read_write_mode(TRUE);
/*
* write the bytes to the file - on success (zero write_to_file
* return), return nil, on failure (non-zero write_to_file return),
* return true
*/
if (arr->write_to_file(get_ext()->fp, idx, len))
{
/* we failed to write the bytes - throw an I/O exception */
G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
0, "file I/O error");
}
/* discard our gc protection */
G_stk->discard();
/* no return value - return nil by default */
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - get the seek position
*/
int CVmObjFile::getp_get_pos(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
unsigned long cur_pos;
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* get the current seek position */
cur_pos = osfpos(get_ext()->fp);
/* if this is a resource file, adjust for the base offset */
cur_pos -= get_ext()->res_start;
/* return the seek position */
retval->set_int(cur_pos);
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - set the seek position
*/
int CVmObjFile::getp_set_pos(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(1);
int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
unsigned long pos;
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* note the seeking operation */
note_file_seek(vmg_ self, TRUE);
/* retrieve the target seek position */
pos = CVmBif::pop_long_val(vmg0_);
/* adjust for the resource base offset */
pos += get_ext()->res_start;
/*
* if this is a resource file, move the position at most to the first
* byte after the end of the resource
*/
if (is_res_file && pos > get_ext()->res_end)
pos = get_ext()->res_end;
/* seek to the new position */
osfseek(get_ext()->fp, pos, OSFSK_SET);
/* no return value */
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - set position to end of file
*/
int CVmObjFile::getp_set_pos_end(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* note the seeking operation */
note_file_seek(vmg_ self, TRUE);
/* handle according to whether it's a resource or not */
if (is_res_file)
{
/* resource - seek to the first byte after the resource data */
osfseek(get_ext()->fp, get_ext()->res_end, OSFSK_SET);
}
else
{
/* normal file - simply seek to the end of the file */
osfseek(get_ext()->fp, 0, OSFSK_END);
}
/* no return value */
retval->set_nil();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Property evaluator - get size
*/
int CVmObjFile::getp_get_size(VMG_ vm_obj_id_t self, vm_val_t *retval,
uint *argc)
{
static CVmNativeCodeDesc desc(0);
int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
/* check arguments */
if (get_prop_check_argc(retval, argc, &desc))
return TRUE;
/* make sure we are allowed to perform operations on the file */
check_valid_file(vmg0_);
/* note the seeking operation */
note_file_seek(vmg_ self, TRUE);
/* handle according to whether it's a resource or not */
if (is_res_file)
{
/* resource - we know the size from the resource descriptor */
retval->set_int(get_ext()->res_end - get_ext()->res_start + 1);
}
else
{
osfildef *fp = get_ext()->fp;
unsigned long cur_pos;
/*
* It's a normal file. Remember the current seek position, then
* seek to the end of the file.
*/
cur_pos = osfpos(fp);
osfseek(fp, 0, OSFSK_END);
/* the current position gives us the length of the file */
retval->set_int(osfpos(fp));
/* seek back to where we started */
osfseek(fp, cur_pos, OSFSK_SET);
}
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Refill the read buffer. Returns true if the buffer contains any data
* on return, false if we're at end of file.
*/
int vmobjfile_readbuf_t::refill(CCharmapToUni *charmap,
osfildef *fp, int is_res_file,
unsigned long res_seek_end)
{
unsigned long read_limit;
/* if the buffer isn't empty, ignore the request */
if (rem != 0)
return TRUE;
/* presume there's no read limit */
read_limit = 0;
/* if it's a resource file, limit the size */
if (is_res_file)
{
unsigned long cur_seek_ofs;
/* make sure we're not already past the end */
cur_seek_ofs = osfpos(fp);
if (cur_seek_ofs >= res_seek_end)
return FALSE;
/* calculate the amount of data remaining in the resource */
read_limit = res_seek_end - cur_seek_ofs;
}
/* read the text */
rem = charmap->read_file(fp, buf, sizeof(buf), read_limit);
/* read from the start of the buffer */
ptr.set(buf);
/* indicate that we have more data to read */
return (rem != 0);
}
|