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