cfad47cfa3/tads3/vmfilobj.cpp

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