1 /*
2 * Copyright (c) 2001, 2002 Michael J. Roberts. All Rights Reserved.
3 *
4 * Please see the accompanying license file, LICENSE.TXT, for information
5 * on using and copying this software.
6 */
7 /*
8 Name
9 vmfilobj.h - File object metaclass
10 Function
11 Implements an intrinsic class interface to operating system file I/O.
12 Notes
13
14 Modified
15 06/28/01 MJRoberts - Creation
16 */
17
18 #include <assert.h>
19 #include "t3std.h"
20 #include "charmap.h"
21 #include "vmerr.h"
22 #include "vmerrnum.h"
23 #include "vmtype.h"
24 #include "vmbif.h"
25 #include "vmcset.h"
26 #include "vmstack.h"
27 #include "vmmeta.h"
28 #include "vmrun.h"
29 #include "vmglob.h"
30 #include "vmfile.h"
31 #include "vmobj.h"
32 #include "vmstr.h"
33 #include "vmfilobj.h"
34 #include "vmpredef.h"
35 #include "vmundo.h"
36 #include "vmbytarr.h"
37 #include "vmbignum.h"
38 #include "vmhost.h"
39
40
41 /* ------------------------------------------------------------------------ */
42 /*
43 * statics
44 */
45
46 /* metaclass registration object */
47 static CVmMetaclassFile metaclass_reg_obj;
48 CVmMetaclass *CVmObjFile::metaclass_reg_ = &metaclass_reg_obj;
49
50 /* function table */
51 int (CVmObjFile::
52 *CVmObjFile::func_table_[])(VMG_ vm_obj_id_t self,
53 vm_val_t *retval, uint *argc) =
54 {
55 &CVmObjFile::getp_undef,
56 &CVmObjFile::getp_open_text,
57 &CVmObjFile::getp_open_data,
58 &CVmObjFile::getp_open_raw,
59 &CVmObjFile::getp_get_charset,
60 &CVmObjFile::getp_set_charset,
61 &CVmObjFile::getp_close_file,
62 &CVmObjFile::getp_read_file,
63 &CVmObjFile::getp_write_file,
64 &CVmObjFile::getp_read_bytes,
65 &CVmObjFile::getp_write_bytes,
66 &CVmObjFile::getp_get_pos,
67 &CVmObjFile::getp_set_pos,
68 &CVmObjFile::getp_set_pos_end,
69 &CVmObjFile::getp_open_res_text,
70 &CVmObjFile::getp_open_res_raw,
71 &CVmObjFile::getp_get_size
72 };
73
74 /*
75 * Vector indices - we only need to define these for the static functions,
76 * since we only need them to decode calls to call_stat_prop().
77 */
78 enum vmobjfil_meta_fnset
79 {
80 VMOBJFILE_OPEN_TEXT = 1,
81 VMOBJFILE_OPEN_DATA = 2,
82 VMOBJFILE_OPEN_RAW = 3,
83 VMOBJFILE_OPEN_RES_TEXT = 14,
84 VMOBJFILE_OPEN_RES_RAW = 15
85 };
86
87
88 /* ------------------------------------------------------------------------ */
89 /*
90 * Create from stack
91 */
create_from_stack(VMG_ const uchar ** pc_ptr,uint argc)92 vm_obj_id_t CVmObjFile::create_from_stack(VMG_ const uchar **pc_ptr,
93 uint argc)
94 {
95 /*
96 * we can't be created with 'new' - we can only be created via our
97 * static creator methods (openTextFile, openDataFile, openRawFile)
98 */
99 err_throw(VMERR_BAD_DYNAMIC_NEW);
100
101 /* not reached, but the compiler doesn't know that */
102 return VM_INVALID_OBJ;
103 }
104
105 /* ------------------------------------------------------------------------ */
106 /*
107 * Create with no contents
108 */
create(VMG_ int in_root_set)109 vm_obj_id_t CVmObjFile::create(VMG_ int in_root_set)
110 {
111 vm_obj_id_t id = vm_new_id(vmg_ in_root_set, TRUE, FALSE);
112
113 /* instantiate the object */
114 new (vmg_ id) CVmObjFile();
115
116 /* files are always transient */
117 G_obj_table->set_obj_transient(id);
118
119 /* return the new ID */
120 return id;
121 }
122
123 /*
124 * Create with the given character set object and file handle.
125 */
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)126 vm_obj_id_t CVmObjFile::create(VMG_ int in_root_set,
127 vm_obj_id_t charset, osfildef *fp,
128 unsigned long flags, int mode, int access,
129 int create_readbuf,
130 unsigned long res_start, unsigned long res_end)
131 {
132 vm_obj_id_t id = vm_new_id(vmg_ in_root_set, TRUE, FALSE);
133
134 /* instantiate the object */
135 new (vmg_ id) CVmObjFile(vmg_ charset, fp, flags, mode, access,
136 create_readbuf, res_start, res_end);
137
138 /* files are always transient */
139 G_obj_table->set_obj_transient(id);
140
141 /* return the ID */
142 return id;
143 }
144
145 /* ------------------------------------------------------------------------ */
146 /*
147 * Instantiate
148 */
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)149 CVmObjFile::CVmObjFile(VMG_ vm_obj_id_t charset, osfildef *fp,
150 unsigned long flags, int mode, int access,
151 int create_readbuf,
152 unsigned long res_start, unsigned long res_end)
153 {
154 /* allocate and initialize our extension */
155 ext_ = 0;
156 alloc_ext(vmg_ charset, fp, flags, mode, access, create_readbuf,
157 res_start, res_end);
158 }
159
160 /*
161 * Allocate and initialize our extension
162 */
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)163 void CVmObjFile::alloc_ext(VMG_ vm_obj_id_t charset, osfildef *fp,
164 unsigned long flags, int mode, int access,
165 int create_readbuf,
166 unsigned long res_start, unsigned long res_end)
167 {
168 size_t siz;
169
170 /*
171 * if we already have an extension, delete it (and release our
172 * underlying system file, if any)
173 */
174 notify_delete(vmg_ FALSE);
175
176 /*
177 * Figure the needed size. We need at least the standard extension;
178 * if we also need a read buffer, figure in space for that as well.
179 */
180 siz = sizeof(vmobjfile_ext_t);
181 if (create_readbuf)
182 siz += sizeof(vmobjfile_readbuf_t);
183
184 /* allocate space for our extension structure */
185 ext_ = (char *)G_mem->get_var_heap()->alloc_mem(siz, this);
186
187 /* store the data we received from the caller */
188 get_ext()->fp = fp;
189 get_ext()->charset = charset;
190 get_ext()->flags = flags;
191 get_ext()->mode = (unsigned char)mode;
192 get_ext()->access = (unsigned char)access;
193 get_ext()->res_start = res_start;
194 get_ext()->res_end = res_end;
195
196 /*
197 * point to our read buffer, for which we allocated space contiguously
198 * with and immediately following our extension, if we have one
199 */
200 if (create_readbuf)
201 {
202 /* point to our read buffer object */
203 get_ext()->readbuf = (vmobjfile_readbuf_t *)(get_ext() + 1);
204
205 /* initialize the read buffer with no initial data */
206 get_ext()->readbuf->rem = 0;
207 get_ext()->readbuf->ptr.set(0);
208 }
209 else
210 {
211 /* there's no read buffer at all */
212 get_ext()->readbuf = 0;
213 }
214 }
215
216 /* ------------------------------------------------------------------------ */
217 /*
218 * Notify of deletion
219 */
notify_delete(VMG_ int)220 void CVmObjFile::notify_delete(VMG_ int /*in_root_set*/)
221 {
222 /* if we have an extension, clean it up */
223 if (ext_ != 0)
224 {
225 /* close our file if we have one */
226 if (get_ext()->fp != 0)
227 osfcls(get_ext()->fp);
228
229 /* free our extension */
230 G_mem->get_var_heap()->free_mem(ext_);
231 }
232 }
233
234 /* ------------------------------------------------------------------------ */
235 /*
236 * set a property
237 */
set_prop(VMG_ class CVmUndo *,vm_obj_id_t,vm_prop_id_t,const vm_val_t *)238 void CVmObjFile::set_prop(VMG_ class CVmUndo *,
239 vm_obj_id_t, vm_prop_id_t,
240 const vm_val_t *)
241 {
242 err_throw(VMERR_INVALID_SETPROP);
243 }
244
245 /* ------------------------------------------------------------------------ */
246 /*
247 * get a property
248 */
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)249 int CVmObjFile::get_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval,
250 vm_obj_id_t self, vm_obj_id_t *source_obj,
251 uint *argc)
252 {
253 ushort func_idx;
254
255 /* translate the property index to an index into our function table */
256 func_idx = G_meta_table
257 ->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop);
258
259 /* call the appropriate function */
260 if ((this->*func_table_[func_idx])(vmg_ self, retval, argc))
261 {
262 *source_obj = metaclass_reg_->get_class_obj(vmg0_);
263 return TRUE;
264 }
265
266 /* inherit default handling */
267 return CVmObject::get_prop(vmg_ prop, retval, self, source_obj, argc);
268 }
269
270 /*
271 * call a static property
272 */
call_stat_prop(VMG_ vm_val_t * result,const uchar ** pc_ptr,uint * argc,vm_prop_id_t prop)273 int CVmObjFile::call_stat_prop(VMG_ vm_val_t *result,
274 const uchar **pc_ptr, uint *argc,
275 vm_prop_id_t prop)
276 {
277 /* translate the property into a function vector index */
278 switch(G_meta_table
279 ->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop))
280 {
281 case VMOBJFILE_OPEN_TEXT:
282 return s_getp_open_text(vmg_ result, argc, FALSE);
283
284 case VMOBJFILE_OPEN_DATA:
285 return s_getp_open_data(vmg_ result, argc);
286
287 case VMOBJFILE_OPEN_RAW:
288 return s_getp_open_raw(vmg_ result, argc, FALSE);
289
290 case VMOBJFILE_OPEN_RES_TEXT:
291 return s_getp_open_text(vmg_ result, argc, TRUE);
292
293 case VMOBJFILE_OPEN_RES_RAW:
294 return s_getp_open_raw(vmg_ result, argc, TRUE);
295
296 default:
297 /* it's not one of ours - inherit from the base object metaclass */
298 return CVmObject::call_stat_prop(vmg_ result, pc_ptr, argc, prop);
299 }
300 }
301
302 /* ------------------------------------------------------------------------ */
303 /*
304 * load from an image file
305 */
load_from_image(VMG_ vm_obj_id_t self,const char * ptr,size_t siz)306 void CVmObjFile::load_from_image(VMG_ vm_obj_id_t self,
307 const char *ptr, size_t siz)
308 {
309 /* load from the image data */
310 load_image_data(vmg_ ptr, siz);
311
312 /*
313 * save our image data pointer in the object table, so that we can
314 * access it (without storing it ourselves) during a reload
315 */
316 G_obj_table->save_image_pointer(self, ptr, siz);
317 }
318
319 /*
320 * reload the object from image data
321 */
reload_from_image(VMG_ vm_obj_id_t,const char * ptr,size_t siz)322 void CVmObjFile::reload_from_image(VMG_ vm_obj_id_t /*self*/,
323 const char *ptr, size_t siz)
324 {
325 /* load the image data */
326 load_image_data(vmg_ ptr, siz);
327 }
328
329 /*
330 * load or re-load image data
331 */
load_image_data(VMG_ const char * ptr,size_t siz)332 void CVmObjFile::load_image_data(VMG_ const char *ptr, size_t siz)
333 {
334 vm_obj_id_t charset;
335 unsigned long flags;
336 int mode;
337 int access;
338
339 /* read our character set */
340 charset = vmb_get_objid(ptr);
341 ptr += VMB_OBJECT_ID;
342
343 /* get the mode and access values */
344 mode = (unsigned char)*ptr++;
345 access = (unsigned char)*ptr++;
346
347 /* get the flags */
348 flags = osrp4(ptr);
349
350 /*
351 * add in the out-of-sync flag, since we've restored the state from a
352 * past state and thus we're out of sync with the external file system
353 * environment
354 */
355 flags |= VMOBJFILE_OUT_OF_SYNC;
356
357 /*
358 * Initialize our extension - we have no underlying native file
359 * handle, since the file is out of sync by virtue of being loaded
360 * from a previously saved image state. Note that we don't need a
361 * read buffer because the file is inherently out of sync and thus
362 * cannot be read.
363 */
364 alloc_ext(vmg_ charset, 0, flags, mode, access, FALSE, 0, 0);
365 }
366
367 /* ------------------------------------------------------------------------ */
368 /*
369 * save to a file
370 */
save_to_file(VMG_ class CVmFile * fp)371 void CVmObjFile::save_to_file(VMG_ class CVmFile *fp)
372 {
373 /* files are always transient, so should never be saved */
374 assert(FALSE);
375 }
376
377 /*
378 * restore from a file
379 */
restore_from_file(VMG_ vm_obj_id_t self,CVmFile * fp,CVmObjFixup *)380 void CVmObjFile::restore_from_file(VMG_ vm_obj_id_t self,
381 CVmFile *fp, CVmObjFixup *)
382 {
383 /* files are always transient, so should never be savd */
384 }
385
386 /* ------------------------------------------------------------------------ */
387 /*
388 * Mark as referenced all of the objects to which we refer
389 */
mark_refs(VMG_ uint state)390 void CVmObjFile::mark_refs(VMG_ uint state)
391 {
392 /* mark our character set object, if we have one */
393 if (get_ext()->charset != VM_INVALID_OBJ)
394 G_obj_table->mark_all_refs(get_ext()->charset, state);
395 }
396
397 /* ------------------------------------------------------------------------ */
398 /*
399 * Note that we're seeking within the file.
400 */
note_file_seek(VMG_ vm_obj_id_t self,int is_explicit)401 void CVmObjFile::note_file_seek(VMG_ vm_obj_id_t self, int is_explicit)
402 {
403 /*
404 * if it's an explicit seek, invalidate our internal read buffer and
405 * note that the stdio buffers have been invalidated
406 */
407 if (is_explicit)
408 {
409 /* the read buffer (if we have one) is invalid after a seek */
410 if (get_ext()->readbuf != 0)
411 get_ext()->readbuf->rem = 0;
412
413 /*
414 * mark the last operation as clearing stdio buffering - when we
415 * explicitly seek, stdio automatically invalidates its internal
416 * buffers
417 */
418 get_ext()->flags &= ~VMOBJFILE_STDIO_BUF_DIRTY;
419 }
420 }
421
422 /*
423 * Update stdio buffering for a switch between read and write mode, if
424 * necessary. Any time we perform consecutive read and write operations,
425 * stdio requires us to explicitly seek when performing consecutive
426 * dissimilar operations. In other words, if we read then write, we must
427 * seek before the write, and likewise if we write then read.
428 */
switch_read_write_mode(int writing)429 void CVmObjFile::switch_read_write_mode(int writing)
430 {
431 /* if we're writing, invalidate the read buffer */
432 if (writing && get_ext()->readbuf != 0)
433 get_ext()->readbuf->rem = 0;
434
435 /*
436 * if we just performed a read or write operation, we must seek if
437 * we're performing the opposite type of operation now
438 */
439 if ((get_ext()->flags & VMOBJFILE_STDIO_BUF_DIRTY) != 0)
440 {
441 int was_writing;
442
443 /* check what type of operation we did last */
444 was_writing = ((get_ext()->flags & VMOBJFILE_LAST_OP_WRITE) != 0);
445
446 /*
447 * if we're switching operations, explicitly seek to the current
448 * location to flush the stdio buffers
449 */
450 if ((writing && !was_writing) || (!writing && was_writing))
451 osfseek(get_ext()->fp, osfpos(get_ext()->fp), OSFSK_SET);
452 }
453
454 /*
455 * remember that this operation is stdio-buffered, so that we'll know
456 * we need to seek if we perform the opposite type of application
457 * after this one
458 */
459 get_ext()->flags |= VMOBJFILE_STDIO_BUF_DIRTY;
460
461 /* remember which type of operation we're performing */
462 if (writing)
463 get_ext()->flags |= VMOBJFILE_LAST_OP_WRITE;
464 else
465 get_ext()->flags &= ~VMOBJFILE_LAST_OP_WRITE;
466 }
467
468 /* ------------------------------------------------------------------------ */
469 /*
470 * Static property evaluator - open a text file
471 */
s_getp_open_text(VMG_ vm_val_t * retval,uint * in_argc,int is_resource_file)472 int CVmObjFile::s_getp_open_text(VMG_ vm_val_t *retval, uint *in_argc,
473 int is_resource_file)
474 {
475 uint argc = (in_argc != 0 ? *in_argc : 0);
476 static CVmNativeCodeDesc desc_file(2, 1);
477 static CVmNativeCodeDesc desc_res(1, 1);
478 char fname[OSFNMAX];
479 int access;
480 vm_obj_id_t cset_obj;
481 osfildef *fp;
482 int create_readbuf;
483 unsigned long res_start;
484 unsigned long res_end;
485 unsigned int flags;
486
487 /* check arguments */
488 if (get_prop_check_argc(retval, in_argc,
489 is_resource_file ? &desc_res : &desc_file))
490 return TRUE;
491
492 /* initialize the flags to indicate a text-mode file */
493 flags = 0;
494
495 /* add the resource-file flag if appropriate */
496 if (is_resource_file)
497 flags |= VMOBJFILE_IS_RESOURCE;
498
499 /* presume we can use the entire file */
500 res_start = 0;
501 res_end = 0;
502
503 /* retrieve the filename */
504 CVmBif::pop_str_val_fname(vmg_ fname, sizeof(fname));
505
506 /*
507 * retrieve the access mode; if it's a resource file, the mode is
508 * implicitly 'read'
509 */
510 if (is_resource_file)
511 access = VMOBJFILE_ACCESS_READ;
512 else
513 access = CVmBif::pop_int_val(vmg0_);
514
515 /* presume we won't need a read buffer */
516 create_readbuf = FALSE;
517
518 /* if there's a character set name or object, retrieve it */
519 if (argc > 2)
520 {
521 /*
522 * check to see if it's a CharacterSet object; if it's not, it
523 * must be a string giving the character set name
524 */
525 if (G_stk->get(0)->typ == VM_OBJ
526 && CVmObjCharSet::is_charset(vmg_ G_stk->get(0)->val.obj))
527 {
528 /* retrieve the CharacterSet reference */
529 cset_obj = CVmBif::pop_obj_val(vmg0_);
530 }
531 else
532 {
533 const char *str;
534 size_t len;
535
536 /* it's not a CharacterSet, so it must be a character set name */
537 str = G_stk->get(0)->get_as_string(vmg0_);
538 if (str == 0)
539 err_throw(VMERR_BAD_TYPE_BIF);
540
541 /* get the length and skip the length prefix */
542 len = vmb_get_len(str);
543 str += VMB_LEN;
544
545 /* create a mapper for the given name */
546 cset_obj = CVmObjCharSet::create(vmg_ FALSE, str, len);
547 }
548 }
549 else
550 {
551 /* no character set is specified - use US-ASCII by default */
552 cset_obj = CVmObjCharSet::create(vmg_ FALSE, "us-ascii", 8);
553 }
554
555 /* push the character map object onto the stack for gc protection */
556 G_stk->push()->set_obj(cset_obj);
557
558 /*
559 * Check the file safety mode to ensure this operation is allowed.
560 * Reading resources is always allowed, regardless of the safety mode,
561 * since resources are read-only and are inherently constrained in the
562 * paths they can access.
563 */
564 if (!is_resource_file)
565 check_safety_for_open(vmg_ fname, access);
566
567 /* open the file for reading or writing, as appropriate */
568 switch(access)
569 {
570 case VMOBJFILE_ACCESS_READ:
571 /* open a resource file or file system file, as appropriate */
572 if (is_resource_file)
573 {
574 unsigned long res_len;
575
576 /* it's a resource - open it */
577 fp = G_host_ifc->find_resource(fname, strlen(fname), &res_len);
578
579 /*
580 * if we found the resource, note the start and end seek
581 * positions, so we can limit reading of the underlying file
582 * to the section that contains the resource data
583 */
584 if (fp != 0)
585 {
586 /* the file is initially at the start of the resource data */
587 res_start = osfpos(fp);
588
589 /* note the offset of the first byte after the resource */
590 res_end = res_start + res_len;
591 }
592 }
593 else
594 {
595 /*
596 * Not a resource - open an ordinary text file for reading.
597 * Even though we're going to treat the file as a text file,
598 * open it in binary mode, since we'll do our own universal
599 * newline translations; this allows us to work with files in
600 * any character set, and using almost any newline
601 * conventions, so files copied from other systems will be
602 * fully usable even if they haven't been fixed up to local
603 * conventions.
604 */
605 fp = osfoprb(fname, OSFTTEXT);
606 }
607
608 /* make sure we opened it successfully */
609 if (fp == 0)
610 G_interpreter->throw_new_class(vmg_ G_predef->file_not_found_exc,
611 0, "file not found");
612
613 /* we need a read buffer */
614 create_readbuf = TRUE;
615 break;
616
617 case VMOBJFILE_ACCESS_WRITE:
618 /* open for writing */
619 fp = osfopwt(fname, OSFTTEXT);
620
621 /* make sure we created it successfully */
622 if (fp == 0)
623 G_interpreter->throw_new_class(vmg_ G_predef->file_creation_exc,
624 0, "error creating file");
625 break;
626
627 case VMOBJFILE_ACCESS_RW_KEEP:
628 /* open in text mode for read/write, keeping existing contents */
629 fp = osfoprwt(fname, OSFTTEXT);
630
631 /* make sure we were able to find or create the file */
632 if (fp == 0)
633 G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
634 0, "error opening file");
635 break;
636
637 case VMOBJFILE_ACCESS_RW_TRUNC:
638 /* open in text mode for read/write, truncating existing contents */
639 fp = osfoprwtt(fname, OSFTBIN);
640
641 /* make sure we were successful */
642 if (fp == 0)
643 G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
644 0, "error opening file");
645 break;
646
647 default:
648 err_throw(VMERR_BAD_VAL_BIF);
649 }
650
651 /* create our file object */
652 retval->set_obj(create(vmg_ FALSE, cset_obj, fp, flags,
653 VMOBJFILE_MODE_TEXT, access, create_readbuf,
654 res_start, res_end));
655
656 /* discard gc protection */
657 G_stk->discard();
658
659 /* handled */
660 return TRUE;
661 }
662
663 /*
664 * Static property evaluator - open a data file
665 */
s_getp_open_data(VMG_ vm_val_t * retval,uint * argc)666 int CVmObjFile::s_getp_open_data(VMG_ vm_val_t *retval, uint *argc)
667 {
668 /* use the generic binary file opener in 'data' mode */
669 return open_binary(vmg_ retval, argc, VMOBJFILE_MODE_DATA, FALSE);
670 }
671
672 /*
673 * Static property evaluator - open a raw file
674 */
s_getp_open_raw(VMG_ vm_val_t * retval,uint * argc,int is_resource_file)675 int CVmObjFile::s_getp_open_raw(VMG_ vm_val_t *retval, uint *argc,
676 int is_resource_file)
677 {
678 /* use the generic binary file opener in 'raw' mode */
679 return open_binary(vmg_ retval, argc, VMOBJFILE_MODE_RAW,
680 is_resource_file);
681 }
682
683 /*
684 * Generic binary file opener - common to 'data' and 'raw' files
685 */
open_binary(VMG_ vm_val_t * retval,uint * argc,int mode,int is_resource_file)686 int CVmObjFile::open_binary(VMG_ vm_val_t *retval, uint *argc, int mode,
687 int is_resource_file)
688 {
689 static CVmNativeCodeDesc file_desc(2);
690 static CVmNativeCodeDesc res_desc(1);
691 char fname[OSFNMAX];
692 int access;
693 osfildef *fp;
694 unsigned long res_start;
695 unsigned long res_end;
696 unsigned int flags;
697
698 /* check arguments */
699 if (get_prop_check_argc(retval, argc,
700 is_resource_file ? &res_desc : &file_desc))
701 return TRUE;
702
703 /* initialize the flags */
704 flags = 0;
705
706 /* set the resource-file flag, if appropriate */
707 if (is_resource_file)
708 flags |= VMOBJFILE_IS_RESOURCE;
709
710 /* presume we can use the entire file */
711 res_start = 0;
712 res_end = 0;
713
714 /* retrieve the filename */
715 CVmBif::pop_str_val_fname(vmg_ fname, sizeof(fname));
716
717 /*
718 * retrieve the access mode - if it's a resource file, the mode is
719 * implicitly 'read'
720 */
721 if (is_resource_file)
722 access = VMOBJFILE_ACCESS_READ;
723 else
724 access = CVmBif::pop_int_val(vmg0_);
725
726 /*
727 * check the file safety mode to ensure this operation is allowed (but
728 * we don't have to do this for resource files, since resource files
729 * are always readable regardless of the safety mode)
730 */
731 if (!is_resource_file)
732 check_safety_for_open(vmg_ fname, access);
733
734 /* open the file in binary mode, with the desired access type */
735 switch(access)
736 {
737 case VMOBJFILE_ACCESS_READ:
738 /* open the resource or ordinary file, as appropriate */
739 if (is_resource_file)
740 {
741 unsigned long res_len;
742
743 /* it's a resource - open it */
744 fp = G_host_ifc->find_resource(fname, strlen(fname), &res_len);
745
746 /*
747 * if we found the resource, note the start and end seek
748 * positions, so we can limit reading of the underlying file
749 * to the section that contains the resource data
750 */
751 if (fp != 0)
752 {
753 /* the file is initially at the start of the resource data */
754 res_start = osfpos(fp);
755
756 /* note the offset of the first byte after the resource */
757 res_end = res_start + res_len;
758 }
759 }
760 else
761 {
762 /* open the ordinary file in binary mode for reading only */
763 fp = osfoprb(fname, OSFTBIN);
764 }
765
766 /* make sure we were able to find it and open it */
767 if (fp == 0)
768 G_interpreter->throw_new_class(vmg_ G_predef->file_not_found_exc,
769 0, "file not found");
770 break;
771
772 case VMOBJFILE_ACCESS_WRITE:
773 /* open in binary mode for writing only */
774 fp = osfopwb(fname, OSFTBIN);
775
776 /* make sure we were able to create the file successfully */
777 if (fp == 0)
778 G_interpreter->throw_new_class(vmg_ G_predef->file_creation_exc,
779 0, "error creating file");
780 break;
781
782 case VMOBJFILE_ACCESS_RW_KEEP:
783 /* open in binary mode for read/write, keeping existing contents */
784 fp = osfoprwb(fname, OSFTBIN);
785
786 /* make sure we were able to find or create the file */
787 if (fp == 0)
788 G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
789 0, "error opening file");
790 break;
791
792 case VMOBJFILE_ACCESS_RW_TRUNC:
793 /* open in binary mode for read/write, truncating existing contents */
794 fp = osfoprwtb(fname, OSFTBIN);
795
796 /* make sure we were successful */
797 if (fp == 0)
798 G_interpreter->throw_new_class(vmg_ G_predef->file_open_exc,
799 0, "error opening file");
800 break;
801
802 default:
803 err_throw(VMERR_BAD_VAL_BIF);
804 }
805
806 /* create our file object */
807 retval->set_obj(create(vmg_ FALSE, VM_INVALID_OBJ, fp,
808 flags, mode, access, FALSE, res_start, res_end));
809
810 /* handled */
811 return TRUE;
812 }
813
814 /* ------------------------------------------------------------------------ */
815 /*
816 * Check the safety settings to determine if an open operation is allowed.
817 * If the access is not allowed, we'll throw an error.
818 */
check_safety_for_open(VMG_ const char * fname,int access)819 void CVmObjFile::check_safety_for_open(VMG_ const char *fname, int access)
820 {
821 int safety;
822 int in_same_dir;
823
824 /* get the current file safety level from the host application */
825 safety = G_host_ifc->get_io_safety();
826
827 /*
828 * Check to see if the file is in the current directory - if not, we
829 * may have to disallow the operation based on safety level settings.
830 * If the file has any sort of directory prefix, assume it's not in
831 * the same directory; if not, it must be. This is actually overly
832 * conservative, since the path may be a relative path or even an
833 * absolute path that points to the current directory, but the
834 * important thing is whether we're allowing files to specify paths at
835 * all.
836 */
837 in_same_dir = (os_get_root_name((char *)fname) == fname);
838
839 /* check for conformance with the safety level setting */
840 switch (access)
841 {
842 case VMOBJFILE_ACCESS_READ:
843 /*
844 * we want only read access - we can't read at all if the safety
845 * level isn't READ_CUR or below, and we must be at level
846 * READ_ANY_WRITE_CUR or lower to read from a file not in the
847 * current directory
848 */
849 if (safety > VM_IO_SAFETY_READ_CUR
850 || (!in_same_dir && safety > VM_IO_SAFETY_READ_ANY_WRITE_CUR))
851 {
852 /* this operation is not allowed - throw an error */
853 G_interpreter->throw_new_class(vmg_ G_predef->file_safety_exc,
854 0, "prohibited file access");
855 }
856 break;
857
858 case VMOBJFILE_ACCESS_WRITE:
859 case VMOBJFILE_ACCESS_RW_KEEP:
860 case VMOBJFILE_ACCESS_RW_TRUNC:
861 /*
862 * writing - we must be safety level of at least READWRITE_CUR to
863 * write at all, and we must be at level MINIMUM to write a file
864 * that's not in the current directory
865 */
866 if (safety > VM_IO_SAFETY_READWRITE_CUR
867 || (!in_same_dir && safety > VM_IO_SAFETY_MINIMUM))
868 {
869 /* this operation is not allowed - throw an error */
870 G_interpreter->throw_new_class(vmg_ G_predef->file_safety_exc,
871 0, "prohibited file access");
872 }
873 break;
874 }
875 }
876
877 /* ------------------------------------------------------------------------ */
878 /*
879 * Property evaluator - get the character set
880 */
getp_get_charset(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)881 int CVmObjFile::getp_get_charset(VMG_ vm_obj_id_t self, vm_val_t *retval,
882 uint *argc)
883 {
884 static CVmNativeCodeDesc desc(0);
885
886 /* check arguments */
887 if (get_prop_check_argc(retval, argc, &desc))
888 return TRUE;
889
890 /* if we have a character set object, return it; otherwise return nil */
891 if (get_ext()->charset != VM_INVALID_OBJ)
892 retval->set_obj(get_ext()->charset);
893 else
894 retval->set_nil();
895
896 /* handled */
897 return TRUE;
898 }
899
900 /* ------------------------------------------------------------------------ */
901 /*
902 * Property evaluator - set the character set
903 */
getp_set_charset(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)904 int CVmObjFile::getp_set_charset(VMG_ vm_obj_id_t self, vm_val_t *retval,
905 uint *argc)
906 {
907 static CVmNativeCodeDesc desc(1);
908
909 /* check arguments */
910 if (get_prop_check_argc(retval, argc, &desc))
911 return TRUE;
912
913 /* make sure it's really a character set object */
914 if (G_stk->get(0)->typ != VM_NIL
915 && (G_stk->get(0)->typ != VM_OBJ
916 || !CVmObjCharSet::is_charset(vmg_ G_stk->get(0)->val.obj)))
917 err_throw(VMERR_BAD_TYPE_BIF);
918
919 /* remember the new character set */
920 if (G_stk->get(0)->typ == VM_NIL)
921 get_ext()->charset = VM_INVALID_OBJ;
922 else
923 get_ext()->charset = G_stk->get(0)->val.obj;
924
925 /* discard the argument */
926 G_stk->discard();
927
928 /* no return value */
929 retval->set_nil();
930
931 /* handled */
932 return TRUE;
933 }
934
935 /* ------------------------------------------------------------------------ */
936 /*
937 * Check that we're in a valid state to perform file operations.
938 */
check_valid_file(VMG0_)939 void CVmObjFile::check_valid_file(VMG0_)
940 {
941 /* if we're 'out of sync', we can't perform any operations on it */
942 if ((get_ext()->flags & VMOBJFILE_OUT_OF_SYNC) != 0)
943 G_interpreter->throw_new_class(vmg_ G_predef->file_sync_exc,
944 0, "file out of sync");
945
946 /* if the file has been closed, we can't perform any operations on it */
947 if (get_ext()->fp == 0)
948 G_interpreter->throw_new_class(vmg_ G_predef->file_closed_exc, 0,
949 "operation attempted on closed file");
950 }
951
952 /*
953 * Check that we have read access
954 */
check_read_access(VMG0_)955 void CVmObjFile::check_read_access(VMG0_)
956 {
957 /* we have read access unless we're in write-only mode */
958 if (get_ext()->access == VMOBJFILE_ACCESS_WRITE)
959 G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc, 0,
960 "wrong file mode");
961 }
962
963 /*
964 * Check that we have write access
965 */
check_write_access(VMG0_)966 void CVmObjFile::check_write_access(VMG0_)
967 {
968 /* we have write access unless we're in read-only mode */
969 if (get_ext()->access == VMOBJFILE_ACCESS_READ)
970 G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc, 0,
971 "wrong file mode");
972 }
973
974
975 /* ------------------------------------------------------------------------ */
976 /*
977 * Property evaluator - close the file
978 */
getp_close_file(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)979 int CVmObjFile::getp_close_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
980 uint *argc)
981 {
982 static CVmNativeCodeDesc desc(0);
983
984 /* check arguments */
985 if (get_prop_check_argc(retval, argc, &desc))
986 return TRUE;
987
988 /* make sure we are allowed to perform operations on the file */
989 check_valid_file(vmg0_);
990
991 /* close the underlying system file */
992 osfcls(get_ext()->fp);
993
994 /* forget the underlying system file, since it's no longer valid */
995 get_ext()->fp = 0;
996
997 /* no return value */
998 retval->set_nil();
999
1000 /* handled */
1001 return TRUE;
1002 }
1003
1004 /* ------------------------------------------------------------------------ */
1005 /*
1006 * Property evaluator - read from the file
1007 */
getp_read_file(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1008 int CVmObjFile::getp_read_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
1009 uint *argc)
1010 {
1011 static CVmNativeCodeDesc desc(0);
1012
1013 /* check arguments */
1014 if (get_prop_check_argc(retval, argc, &desc))
1015 return TRUE;
1016
1017 /* push a self-reference for gc protection */
1018 G_stk->push()->set_obj(self);
1019
1020 /* make sure we are allowed to perform operations on the file */
1021 check_valid_file(vmg0_);
1022
1023 /* check that we have read access */
1024 check_read_access(vmg0_);
1025
1026 /* note the implicit seeking */
1027 note_file_seek(vmg_ self, FALSE);
1028
1029 /* flush stdio buffers as needed and note the read operation */
1030 switch_read_write_mode(FALSE);
1031
1032 /* read according to our mode */
1033 switch(get_ext()->mode)
1034 {
1035 case VMOBJFILE_MODE_TEXT:
1036 /* read a line of text */
1037 read_text_mode(vmg_ retval);
1038 break;
1039
1040 case VMOBJFILE_MODE_DATA:
1041 /* read in data mode */
1042 read_data_mode(vmg_ retval);
1043 break;
1044
1045 case VMOBJFILE_MODE_RAW:
1046 /* can't use this call on this type of file */
1047 G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
1048 0, "wrong file mode");
1049 break;
1050 }
1051
1052 /* discard the gc protection */
1053 G_stk->discard();
1054
1055 /* handled */
1056 return TRUE;
1057 }
1058
1059 /*
1060 * Read a value in text mode
1061 */
read_text_mode(VMG_ vm_val_t * retval)1062 void CVmObjFile::read_text_mode(VMG_ vm_val_t *retval)
1063 {
1064 CVmObjString *str;
1065 size_t str_len;
1066 osfildef *fp = get_ext()->fp;
1067 vmobjfile_readbuf_t *readbuf = get_ext()->readbuf;
1068 CCharmapToUni *charmap;
1069 int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
1070
1071 /* we haven't yet constructed a string */
1072 str = 0;
1073 str_len = 0;
1074
1075 /* get our character mapper */
1076 charmap = ((CVmObjCharSet *)vm_objp(vmg_ get_ext()->charset))
1077 ->get_to_uni(vmg0_);
1078
1079 /* assume we'll fail to read anything, in which case we'll return nil */
1080 retval->set_nil();
1081
1082 /*
1083 * push the nil value - we'll always keep our intermediate value on
1084 * the stack so that the garbage collector will know it's referenced
1085 */
1086 G_stk->push(retval);
1087
1088 /*
1089 * Read a line of text from the file into our buffer. Keep going
1090 * until we read an entire line; we might have to read the line in
1091 * chunks, since the line might end up being longer than our buffer.
1092 */
1093 for (;;)
1094 {
1095 wchar_t found_nl;
1096 char *start;
1097 size_t new_len;
1098 size_t nl_len;
1099
1100 /* replenish the read buffer if it's empty */
1101 if (readbuf->rem == 0
1102 && !readbuf->refill(charmap, fp, is_res_file, get_ext()->res_end))
1103 break;
1104
1105 /* note where we started this chunk */
1106 start = readbuf->ptr.getptr();
1107
1108 /* scan for and remove any trailing newline */
1109 for (found_nl = '\0' ; readbuf->rem != 0 ;
1110 readbuf->ptr.inc(&readbuf->rem))
1111 {
1112 wchar_t cur;
1113
1114 /* get the current character */
1115 cur = readbuf->ptr.getch();
1116
1117 /*
1118 * check for a newline (note that 0x2028 is the unicode line
1119 * separator character)
1120 */
1121 if (cur == '\n' || cur == '\r' || cur == 0x2028)
1122 {
1123 /* note the newline */
1124 found_nl = cur;
1125
1126 /* no need to look any further */
1127 break;
1128 }
1129 }
1130
1131 /* note the length of the current segment */
1132 new_len = readbuf->ptr.getptr() - start;
1133
1134 /*
1135 * if there's a newline character, include an extra byte for the
1136 * '\n' we'll include in the result
1137 */
1138 nl_len = (found_nl != '\0');
1139
1140 /*
1141 * If this is our first segment, construct a new string from this
1142 * chunk; otherwise, add to the existing string.
1143 *
1144 * Note that in either case, if we found a newline in the buffer,
1145 * we will NOT add the actual newline we found to the result
1146 * string. Rather, we'll add a '\n' character to the result
1147 * string, no matter what kind of newline we found. This ensures
1148 * that the data read uses a consistent format, regardless of the
1149 * local system convention where the file was created.
1150 */
1151 if (str == 0)
1152 {
1153 /* create our first segment's string */
1154 retval->set_obj(CVmObjString::
1155 create(vmg_ FALSE, new_len + nl_len));
1156 str = (CVmObjString *)vm_objp(vmg_ retval->val.obj);
1157
1158 /* copy the segment into the string object */
1159 memcpy(str->cons_get_buf(), start, new_len);
1160
1161 /* add a '\n' if we found a newline */
1162 if (found_nl != '\0')
1163 *(str->cons_get_buf() + new_len) = '\n';
1164
1165 /* this is the length of the string so far */
1166 str_len = new_len + nl_len;
1167
1168 /*
1169 * replace the stack placeholder with our string, so the
1170 * garbage collector will know it's still in use
1171 */
1172 G_stk->discard();
1173 G_stk->push(retval);
1174 }
1175 else
1176 {
1177 CVmObjString *new_str;
1178
1179 /*
1180 * create a new string to hold the contents of the old string
1181 * plus the new buffer
1182 */
1183 retval->set_obj(CVmObjString::create(vmg_ FALSE,
1184 str_len + new_len + nl_len));
1185 new_str = (CVmObjString *)vm_objp(vmg_ retval->val.obj);
1186
1187 /* copy the old string into the new string */
1188 memcpy(new_str->cons_get_buf(),
1189 str->get_as_string(vmg0_) + VMB_LEN, str_len);
1190
1191 /* add the new chunk after the copy of the old string */
1192 memcpy(new_str->cons_get_buf() + str_len, start, new_len);
1193
1194 /* add the newline if necessary */
1195 if (found_nl != '\0')
1196 *(new_str->cons_get_buf() + str_len + new_len) = '\n';
1197
1198 /* the new string now replaces the old string */
1199 str = new_str;
1200 str_len += new_len + nl_len;
1201
1202 /*
1203 * replace our old intermediate value on the stack with the
1204 * new string - the old string isn't needed any more, so we
1205 * can leave it unreferenced, but we are still using the new
1206 * string
1207 */
1208 G_stk->discard();
1209 G_stk->push(retval);
1210 }
1211
1212 /* if we found a newline in this segment, we're done */
1213 if (found_nl != '\0')
1214 {
1215 /* skip the newline in the input */
1216 readbuf->ptr.inc(&readbuf->rem);
1217
1218 /* replenish the read buffer if it's empty */
1219 if (readbuf->rem == 0)
1220 readbuf->refill(charmap, fp, is_res_file, get_ext()->res_end);
1221
1222 /*
1223 * check for a complementary newline character, for systems
1224 * that use \n\r or \r\n pairs
1225 */
1226 if (readbuf->rem != 0)
1227 {
1228 wchar_t nxt;
1229
1230 /* get the next character */
1231 nxt = readbuf->ptr.getch();
1232
1233 /* check for a complementary character */
1234 if ((found_nl == '\n' && nxt == '\r')
1235 || (found_nl == '\r' && nxt == '\n'))
1236 {
1237 /*
1238 * we have a pair sequence - skip the second character
1239 * of the sequence
1240 */
1241 readbuf->ptr.inc(&readbuf->rem);
1242 }
1243 }
1244
1245 /* we've found the newline, so we're done with the string */
1246 break;
1247 }
1248 }
1249
1250 /*
1251 * we now can discard the string we've been keeping on the stack to
1252 * for garbage collection protection
1253 */
1254 G_stk->discard();
1255 }
1256
1257 /*
1258 * Read a value in 'data' mode
1259 */
read_data_mode(VMG_ vm_val_t * retval)1260 void CVmObjFile::read_data_mode(VMG_ vm_val_t *retval)
1261 {
1262 char buf[32];
1263 CVmObjString *str_obj;
1264 vm_obj_id_t str_id;
1265 osfildef *fp = get_ext()->fp;
1266
1267 /* read the type flag */
1268 if (osfrb(fp, buf, 1))
1269 {
1270 /* end of file - return nil */
1271 retval->set_nil();
1272 return;
1273 }
1274
1275 /* see what we have */
1276 switch((vm_datatype_t)buf[0])
1277 {
1278 case VMOBJFILE_TAG_INT:
1279 /* read the INT4 value */
1280 if (osfrb(fp, buf, 4))
1281 goto io_error;
1282
1283 /* set the integer value from the buffer */
1284 retval->set_int(osrp4(buf));
1285 break;
1286
1287 case VMOBJFILE_TAG_ENUM:
1288 /* read the UINT4 value */
1289 if (osfrb(fp, buf, 4))
1290 goto io_error;
1291
1292 /* set the 'enum' value */
1293 retval->set_enum(osrp4(buf));
1294 break;
1295
1296 case VMOBJFILE_TAG_STRING:
1297 /*
1298 * read the string's length - note that this length is two
1299 * higher than the actual length of the string, because it
1300 * includes the length prefix bytes
1301 */
1302 if (osfrb(fp, buf, 2))
1303 goto io_error;
1304
1305 /*
1306 * allocate a new string of the required size (deducting two
1307 * bytes from the indicated size, since the string allocator
1308 * only wants to know about the bytes of the string we want to
1309 * store, not the length prefix part)
1310 */
1311 str_id = CVmObjString::create(vmg_ FALSE, osrp2(buf) - 2);
1312 str_obj = (CVmObjString *)vm_objp(vmg_ str_id);
1313
1314 /* read the bytes of the string into the object's buffer */
1315 if (osfrb(fp, str_obj->cons_get_buf(), osrp2(buf) - 2))
1316 goto io_error;
1317
1318 /* success - set the string return value, and we're done */
1319 retval->set_obj(str_id);
1320 break;
1321
1322 case VMOBJFILE_TAG_TRUE:
1323 /* it's a simple 'true' value */
1324 retval->set_true();
1325 break;
1326
1327 case VMOBJFILE_TAG_BIGNUM:
1328 /* read the BigNumber value and return a new BigNumber object */
1329 if (CVmObjBigNum::read_from_data_file(vmg_ retval, fp))
1330 goto io_error;
1331 break;
1332
1333 case VMOBJFILE_TAG_BYTEARRAY:
1334 /* read the ByteArray value and return a new ByteArray object */
1335 if (CVmObjByteArray::read_from_data_file(vmg_ retval, fp))
1336 goto io_error;
1337 break;
1338
1339 default:
1340 /* invalid data - throw an error */
1341 G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
1342 0, "file I/O error");
1343 }
1344
1345 /* done */
1346 return;
1347
1348 io_error:
1349 /*
1350 * we'll come here if we read the type tag correctly but encounter
1351 * an I/O error reading the value - this indicates a corrupted input
1352 * stream, so throw an I/O error
1353 */
1354 G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
1355 0, "file I/O error");
1356 }
1357
1358 /* ------------------------------------------------------------------------ */
1359 /*
1360 * Property evaluator - write to the file
1361 */
getp_write_file(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1362 int CVmObjFile::getp_write_file(VMG_ vm_obj_id_t self, vm_val_t *retval,
1363 uint *argc)
1364 {
1365 static CVmNativeCodeDesc desc(1);
1366 const vm_val_t *argval;
1367
1368 /* check arguments */
1369 if (get_prop_check_argc(retval, argc, &desc))
1370 return TRUE;
1371
1372 /*
1373 * get a pointer to the argument value, but leave it on the stack
1374 * for now to protect against losing it in garbage collection
1375 */
1376 argval = G_stk->get(0);
1377
1378 /* push a self-reference for gc protection */
1379 G_stk->push()->set_obj(self);
1380
1381 /* make sure we are allowed to perform operations on the file */
1382 check_valid_file(vmg0_);
1383
1384 /* check that we have write access */
1385 check_write_access(vmg0_);
1386
1387 /* deal with stdio buffering if we're changing modes */
1388 switch_read_write_mode(TRUE);
1389
1390 /* read according to our mode */
1391 switch(get_ext()->mode)
1392 {
1393 case VMOBJFILE_MODE_TEXT:
1394 /* read a line of text */
1395 write_text_mode(vmg_ argval);
1396 break;
1397
1398 case VMOBJFILE_MODE_DATA:
1399 /* read in data mode */
1400 write_data_mode(vmg_ argval);
1401 break;
1402
1403 case VMOBJFILE_MODE_RAW:
1404 /* can't use this call on this type of file */
1405 G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
1406 0, "wrong file mode");
1407 break;
1408 }
1409
1410 /* discard our gc protection and argument */
1411 G_stk->discard(2);
1412
1413 /* no return value - return nil by default */
1414 retval->set_nil();
1415
1416 /* handled */
1417 return TRUE;
1418 }
1419
1420 /*
1421 * Write a value in text mode
1422 */
write_text_mode(VMG_ const vm_val_t * val)1423 void CVmObjFile::write_text_mode(VMG_ const vm_val_t *val)
1424 {
1425 char conv_buf[128];
1426 vm_val_t new_str;
1427 CCharmapToLocal *charmap;
1428 const char *constp;
1429
1430 /* get our character mapper */
1431 charmap = ((CVmObjCharSet *)vm_objp(vmg_ get_ext()->charset))
1432 ->get_to_local(vmg0_);
1433
1434 /* convert the value to a string */
1435 constp = CVmObjString::cvt_to_str(vmg_ &new_str,
1436 conv_buf, sizeof(conv_buf),
1437 val, 10);
1438
1439 /* write the string value through our character mapper */
1440 if (charmap->write_file(get_ext()->fp, constp + VMB_LEN,
1441 vmb_get_len(constp)))
1442 {
1443 /* the write failed - throw an I/O exception */
1444 G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
1445 0, "file I/O error");
1446 }
1447 }
1448
1449 /*
1450 * Write a value in 'data' mode
1451 */
write_data_mode(VMG_ const vm_val_t * val)1452 void CVmObjFile::write_data_mode(VMG_ const vm_val_t *val)
1453 {
1454 char buf[32];
1455 osfildef *fp = get_ext()->fp;
1456 vm_val_t new_str;
1457 const char *constp;
1458
1459 /* see what type of data we want to put */
1460 switch(val->typ)
1461 {
1462 case VM_INT:
1463 /* put the type in the buffer */
1464 buf[0] = VMOBJFILE_TAG_INT;
1465
1466 /* add the value in INT4 format */
1467 oswp4(buf + 1, val->val.intval);
1468
1469 /* write out the type prefix plus the value */
1470 if (osfwb(fp, buf, 5))
1471 goto io_error;
1472
1473 /* done */
1474 break;
1475
1476 case VM_ENUM:
1477 /* put the type in the buffer */
1478 buf[0] = VMOBJFILE_TAG_ENUM;
1479
1480 /* add the value in INT4 format */
1481 oswp4(buf + 1, val->val.enumval);
1482
1483 /* write out the type prefix plus the value */
1484 if (osfwb(fp, buf, 5))
1485 goto io_error;
1486
1487 /* done */
1488 break;
1489
1490 case VM_SSTRING:
1491 /* get the string value pointer */
1492 constp = val->get_as_string(vmg0_);
1493
1494 write_binary_string:
1495 /* write the type prefix byte */
1496 buf[0] = VMOBJFILE_TAG_STRING;
1497 if (osfwb(fp, buf, 1))
1498 goto io_error;
1499
1500 /*
1501 * write the length prefix - for TADS 2 compatibility, include
1502 * the bytes of the prefix itself in the length count
1503 */
1504 oswp2(buf, vmb_get_len(constp) + 2);
1505 if (osfwb(fp, buf, 2))
1506 goto io_error;
1507
1508 /* write the string's bytes */
1509 if (osfwb(fp, constp + VMB_LEN, vmb_get_len(constp)))
1510 goto io_error;
1511
1512 /* done */
1513 break;
1514
1515 case VM_OBJ:
1516 /*
1517 * Write BigNumber and ByteArray types in special formats. For
1518 * other types, try converting to a string.
1519 */
1520 if (CVmObjBigNum::is_bignum_obj(vmg_ val->val.obj))
1521 {
1522 CVmObjBigNum *bignum;
1523
1524 /* we know it's a BigNumber - cast it properly */
1525 bignum = (CVmObjBigNum *)vm_objp(vmg_ val->val.obj);
1526
1527 /* write the type tag */
1528 buf[0] = VMOBJFILE_TAG_BIGNUM;
1529 if (osfwb(fp, buf, 1))
1530 goto io_error;
1531
1532 /* write it out */
1533 if (bignum->write_to_data_file(fp))
1534 goto io_error;
1535 }
1536 else if (CVmObjByteArray::is_byte_array(vmg_ val->val.obj))
1537 {
1538 CVmObjByteArray *bytarr;
1539
1540 /* we know it's a ByteArray - cast it properly */
1541 bytarr = (CVmObjByteArray *)vm_objp(vmg_ val->val.obj);
1542
1543 /* write the type tag */
1544 buf[0] = VMOBJFILE_TAG_BYTEARRAY;
1545 if (osfwb(fp, buf, 1))
1546 goto io_error;
1547
1548 /* write the array */
1549 if (bytarr->write_to_data_file(fp))
1550 goto io_error;
1551 }
1552 else
1553 {
1554 /*
1555 * Cast it to a string value and write that out. Note that
1556 * we can ignore garbage collection for any new string we've
1557 * created, since we're just calling the OS-level file
1558 * writer, which will never invoke garbage collection.
1559 */
1560 constp = vm_objp(vmg_ val->val.obj)
1561 ->cast_to_string(vmg_ val->val.obj, &new_str);
1562 goto write_binary_string;
1563 }
1564 break;
1565
1566 case VM_TRUE:
1567 /*
1568 * All we need for this is the type tag. Note that we can't
1569 * write nil because we'd have no way of reading it back in - a
1570 * nil return from file_read indicates that we've reached the
1571 * end of the file. So there's no point in writing nil to a
1572 * file.
1573 */
1574 buf[0] = VMOBJFILE_TAG_TRUE;
1575 if (osfwb(fp, buf, 1))
1576 goto io_error;
1577
1578 /* done */
1579 break;
1580
1581 default:
1582 /* other types are not acceptable */
1583 err_throw(VMERR_BAD_TYPE_BIF);
1584 }
1585
1586 /* done */
1587 return;
1588
1589 io_error:
1590 /* the write failed - throw an i/o exception */
1591 G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
1592 0, "file I/O error");
1593 }
1594
1595
1596 /* ------------------------------------------------------------------------ */
1597 /*
1598 * Property evaluator - read raw bytes from the file
1599 */
getp_read_bytes(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * in_argc)1600 int CVmObjFile::getp_read_bytes(VMG_ vm_obj_id_t self, vm_val_t *retval,
1601 uint *in_argc)
1602 {
1603 static CVmNativeCodeDesc desc(1, 2);
1604 uint argc = (in_argc != 0 ? *in_argc : 0);
1605 vm_val_t arr_val;
1606 CVmObjByteArray *arr;
1607 unsigned long idx;
1608 unsigned long len;
1609 int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
1610
1611 /* check arguments */
1612 if (get_prop_check_argc(retval, in_argc, &desc))
1613 return TRUE;
1614
1615 /* make sure we are allowed to perform operations on the file */
1616 check_valid_file(vmg0_);
1617
1618 /* check that we have read access */
1619 check_read_access(vmg0_);
1620
1621 /* we can only use this call on 'raw' files */
1622 if (get_ext()->mode != VMOBJFILE_MODE_RAW)
1623 G_interpreter->throw_new_class(vmg_ G_predef->file_mode_exc,
1624 0, "wrong file mode");
1625
1626 /* retrieve the ByteArray destination */
1627 G_stk->pop(&arr_val);
1628
1629 /* make sure it's really a ByteArray object */
1630 if (arr_val.typ != VM_OBJ
1631 || !CVmObjByteArray::is_byte_array(vmg_ arr_val.val.obj))
1632 err_throw(VMERR_BAD_TYPE_BIF);
1633
1634 /* we know it's a byte array object, so cast it */
1635 arr = (CVmObjByteArray *)vm_objp(vmg_ arr_val.val.obj);
1636
1637 /* presume we'll try to fill the entire array */
1638 idx = 1;
1639 len = arr->get_element_count();
1640
1641 /* if we have a starting index argument, retrieve it */
1642 if (argc >= 2)
1643 idx = (unsigned long)CVmBif::pop_int_val(vmg0_);
1644
1645 /* if we have a length argument, retrieve it */
1646 if (argc >= 3)
1647 len = (unsigned long)CVmBif::pop_int_val(vmg0_);
1648
1649 /* push a self-reference for gc protection */
1650 G_stk->push()->set_obj(self);
1651
1652 /* note the implicit seeking */
1653 note_file_seek(vmg_ self, FALSE);
1654
1655 /* deal with stdio buffering issues if necessary */
1656 switch_read_write_mode(FALSE);
1657
1658 /*
1659 * limit the reading to the remaining data in the file, if it's a
1660 * resource file
1661 */
1662 if (is_res_file)
1663 {
1664 unsigned long cur_seek_pos;
1665
1666 /* check to see where we are relative to the end of the resource */
1667 cur_seek_pos = osfpos(get_ext()->fp);
1668 if (cur_seek_pos >= get_ext()->res_end)
1669 {
1670 /* we're already past the end - there's nothing left */
1671 len = 0;
1672 }
1673 else
1674 {
1675 unsigned long limit;
1676
1677 /* calculate the limit */
1678 limit = get_ext()->res_end - cur_seek_pos;
1679
1680 /* apply the limit if the request exceeds it */
1681 if (len > limit)
1682 len = limit;
1683 }
1684 }
1685
1686 /*
1687 * read the data into the array, and return the number of bytes we
1688 * actually manage to read
1689 */
1690 retval->set_int(arr->read_from_file(get_ext()->fp, idx, len));
1691
1692 /* discard our gc protection */
1693 G_stk->discard();
1694
1695 /* handled */
1696 return TRUE;
1697 }
1698
1699 /* ------------------------------------------------------------------------ */
1700 /*
1701 * Property evaluator - write raw bytes to the file
1702 */
getp_write_bytes(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * in_argc)1703 int CVmObjFile::getp_write_bytes(VMG_ vm_obj_id_t self, vm_val_t *retval,
1704 uint *in_argc)
1705 {
1706 static CVmNativeCodeDesc desc(1, 2);
1707 uint argc = (in_argc != 0 ? *in_argc : 0);
1708 vm_val_t arr_val;
1709 CVmObjByteArray *arr;
1710 unsigned long idx;
1711 unsigned long len;
1712
1713 /* check arguments */
1714 if (get_prop_check_argc(retval, in_argc, &desc))
1715 return TRUE;
1716
1717 /* make sure we are allowed to perform operations on the file */
1718 check_valid_file(vmg0_);
1719
1720 /* check that we have write access */
1721 check_write_access(vmg0_);
1722
1723 /* make sure the byte array argument is really a byte array */
1724 G_stk->pop(&arr_val);
1725 if (arr_val.typ != VM_OBJ
1726 || !CVmObjByteArray::is_byte_array(vmg_ arr_val.val.obj))
1727 err_throw(VMERR_BAD_TYPE_BIF);
1728
1729 /* we know it's a byte array, so we can simply cast it */
1730 arr = (CVmObjByteArray *)vm_objp(vmg_ arr_val.val.obj);
1731
1732 /* assume we'll write the entire byte array */
1733 idx = 1;
1734 len = arr->get_element_count();
1735
1736 /* if we have a starting index, retrieve it */
1737 if (argc >= 2)
1738 idx = (unsigned long)CVmBif::pop_int_val(vmg0_);
1739
1740 /* if we have a length, retrieve it */
1741 if (argc >= 3)
1742 len = (unsigned long)CVmBif::pop_int_val(vmg0_);
1743
1744 /* push a self-reference for gc protection */
1745 G_stk->push()->set_obj(self);
1746
1747 /* flush stdio buffers as needed and note the read operation */
1748 switch_read_write_mode(TRUE);
1749
1750 /*
1751 * write the bytes to the file - on success (zero write_to_file
1752 * return), return nil, on failure (non-zero write_to_file return),
1753 * return true
1754 */
1755 if (arr->write_to_file(get_ext()->fp, idx, len))
1756 {
1757 /* we failed to write the bytes - throw an I/O exception */
1758 G_interpreter->throw_new_class(vmg_ G_predef->file_io_exc,
1759 0, "file I/O error");
1760 }
1761
1762 /* discard our gc protection */
1763 G_stk->discard();
1764
1765 /* no return value - return nil by default */
1766 retval->set_nil();
1767
1768 /* handled */
1769 return TRUE;
1770 }
1771
1772 /* ------------------------------------------------------------------------ */
1773 /*
1774 * Property evaluator - get the seek position
1775 */
getp_get_pos(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1776 int CVmObjFile::getp_get_pos(VMG_ vm_obj_id_t self, vm_val_t *retval,
1777 uint *argc)
1778 {
1779 static CVmNativeCodeDesc desc(0);
1780 unsigned long cur_pos;
1781
1782 /* check arguments */
1783 if (get_prop_check_argc(retval, argc, &desc))
1784 return TRUE;
1785
1786 /* make sure we are allowed to perform operations on the file */
1787 check_valid_file(vmg0_);
1788
1789 /* get the current seek position */
1790 cur_pos = osfpos(get_ext()->fp);
1791
1792 /* if this is a resource file, adjust for the base offset */
1793 cur_pos -= get_ext()->res_start;
1794
1795 /* return the seek position */
1796 retval->set_int(cur_pos);
1797
1798 /* handled */
1799 return TRUE;
1800 }
1801
1802 /* ------------------------------------------------------------------------ */
1803 /*
1804 * Property evaluator - set the seek position
1805 */
getp_set_pos(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1806 int CVmObjFile::getp_set_pos(VMG_ vm_obj_id_t self, vm_val_t *retval,
1807 uint *argc)
1808 {
1809 static CVmNativeCodeDesc desc(1);
1810 int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
1811 unsigned long pos;
1812
1813 /* check arguments */
1814 if (get_prop_check_argc(retval, argc, &desc))
1815 return TRUE;
1816
1817 /* make sure we are allowed to perform operations on the file */
1818 check_valid_file(vmg0_);
1819
1820 /* note the seeking operation */
1821 note_file_seek(vmg_ self, TRUE);
1822
1823 /* retrieve the target seek position */
1824 pos = CVmBif::pop_long_val(vmg0_);
1825
1826 /* adjust for the resource base offset */
1827 pos += get_ext()->res_start;
1828
1829 /*
1830 * if this is a resource file, move the position at most to the first
1831 * byte after the end of the resource
1832 */
1833 if (is_res_file && pos > get_ext()->res_end)
1834 pos = get_ext()->res_end;
1835
1836 /* seek to the new position */
1837 osfseek(get_ext()->fp, pos, OSFSK_SET);
1838
1839 /* no return value */
1840 retval->set_nil();
1841
1842 /* handled */
1843 return TRUE;
1844 }
1845
1846 /* ------------------------------------------------------------------------ */
1847 /*
1848 * Property evaluator - set position to end of file
1849 */
getp_set_pos_end(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1850 int CVmObjFile::getp_set_pos_end(VMG_ vm_obj_id_t self, vm_val_t *retval,
1851 uint *argc)
1852 {
1853 static CVmNativeCodeDesc desc(0);
1854 int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
1855
1856 /* check arguments */
1857 if (get_prop_check_argc(retval, argc, &desc))
1858 return TRUE;
1859
1860 /* make sure we are allowed to perform operations on the file */
1861 check_valid_file(vmg0_);
1862
1863 /* note the seeking operation */
1864 note_file_seek(vmg_ self, TRUE);
1865
1866 /* handle according to whether it's a resource or not */
1867 if (is_res_file)
1868 {
1869 /* resource - seek to the first byte after the resource data */
1870 osfseek(get_ext()->fp, get_ext()->res_end, OSFSK_SET);
1871 }
1872 else
1873 {
1874 /* normal file - simply seek to the end of the file */
1875 osfseek(get_ext()->fp, 0, OSFSK_END);
1876 }
1877
1878 /* no return value */
1879 retval->set_nil();
1880
1881 /* handled */
1882 return TRUE;
1883 }
1884
1885 /* ------------------------------------------------------------------------ */
1886 /*
1887 * Property evaluator - get size
1888 */
getp_get_size(VMG_ vm_obj_id_t self,vm_val_t * retval,uint * argc)1889 int CVmObjFile::getp_get_size(VMG_ vm_obj_id_t self, vm_val_t *retval,
1890 uint *argc)
1891 {
1892 static CVmNativeCodeDesc desc(0);
1893 int is_res_file = ((get_ext()->flags & VMOBJFILE_IS_RESOURCE) != 0);
1894
1895 /* check arguments */
1896 if (get_prop_check_argc(retval, argc, &desc))
1897 return TRUE;
1898
1899 /* make sure we are allowed to perform operations on the file */
1900 check_valid_file(vmg0_);
1901
1902 /* note the seeking operation */
1903 note_file_seek(vmg_ self, TRUE);
1904
1905 /* handle according to whether it's a resource or not */
1906 if (is_res_file)
1907 {
1908 /* resource - we know the size from the resource descriptor */
1909 retval->set_int(get_ext()->res_end - get_ext()->res_start + 1);
1910 }
1911 else
1912 {
1913 osfildef *fp = get_ext()->fp;
1914 unsigned long cur_pos;
1915
1916 /*
1917 * It's a normal file. Remember the current seek position, then
1918 * seek to the end of the file.
1919 */
1920 cur_pos = osfpos(fp);
1921 osfseek(fp, 0, OSFSK_END);
1922
1923 /* the current position gives us the length of the file */
1924 retval->set_int(osfpos(fp));
1925
1926 /* seek back to where we started */
1927 osfseek(fp, cur_pos, OSFSK_SET);
1928 }
1929
1930 /* handled */
1931 return TRUE;
1932 }
1933
1934 /* ------------------------------------------------------------------------ */
1935 /*
1936 * Refill the read buffer. Returns true if the buffer contains any data
1937 * on return, false if we're at end of file.
1938 */
refill(CCharmapToUni * charmap,osfildef * fp,int is_res_file,unsigned long res_seek_end)1939 int vmobjfile_readbuf_t::refill(CCharmapToUni *charmap,
1940 osfildef *fp, int is_res_file,
1941 unsigned long res_seek_end)
1942 {
1943 unsigned long read_limit;
1944
1945 /* if the buffer isn't empty, ignore the request */
1946 if (rem != 0)
1947 return TRUE;
1948
1949 /* presume there's no read limit */
1950 read_limit = 0;
1951
1952 /* if it's a resource file, limit the size */
1953 if (is_res_file)
1954 {
1955 unsigned long cur_seek_ofs;
1956
1957 /* make sure we're not already past the end */
1958 cur_seek_ofs = osfpos(fp);
1959 if (cur_seek_ofs >= res_seek_end)
1960 return FALSE;
1961
1962 /* calculate the amount of data remaining in the resource */
1963 read_limit = res_seek_end - cur_seek_ofs;
1964 }
1965
1966 /* read the text */
1967 rem = charmap->read_file(fp, buf, sizeof(buf), read_limit);
1968
1969 /* read from the start of the buffer */
1970 ptr.set(buf);
1971
1972 /* indicate that we have more data to read */
1973 return (rem != 0);
1974 }
1975
1976
1977