1
/* filename.c-- Low level file i/o and routines to deal */
3
/* Copyright (C) 1996-1999,2001 Robert Masenten */
4
/* This program may be redistributed under the terms of the
5
GNU General Public License, version 2; see agility.h for details. */
7
/* This is part of the source for both AGiliTy: the (Mostly) Universal */
8
/* AGT Interpreter and for the Magx adventure game compiler. */
11
file_type_id must be a type that can be set to 0.
28
#include <sys/stat.h> /* Needed only for file permission bits */
30
#ifdef __STRICT_ANSI__
32
FILE *popen(char *s,char *how);
46
/*----------------------------------------------------------------------*/
48
/*----------------------------------------------------------------------*/
49
const char *extname[]={
51
DA1, DA2, DA3, DA4, DA5, DA6, DSS,
54
pAGX, pINS, pVOC, pCFG,
55
pAGT, pDAT, pMSG, pCMD, pSTD, AGTpSTD};
59
static const char *path_sep=PATH_SEP;
61
static const char *path_sep=NULL;
64
/* This returns the options to use when opening the given file type */
65
/* rw is true if we are writing, false if we are reading. */
66
const char *filetype_info(filetype ft, rbool rw)
68
if (ft<fTTL) return "rb";
69
if (ft==fAGX) return rw ? "wb" : "rb";
70
if (ft==fSAV) return (rw ? "wb" : "rb");
71
if (ft==fTTL || ft==fINS || ft==fVOC) return "rb";
73
if (ft>=fCFG) return (open_as_binary ? "rb" : "r");
75
if (ft>=fCFG) return "rb";
79
return (BATCH_MODE || make_test) ? "w" : "a";
82
if (ft==fLOG) return rw ? "w" : "r";
83
fatal("INTERNAL ERROR: Invalid filetype.");
88
/* Returns true if ft is a possible extension in general context ft_base */
89
static rbool compat_ext(filetype ft, filetype ft_base)
91
if (ft_base==fNONE || ft_base==fDA1 || ft_base==fAGX) { /* Game file */
92
return ( ft>=fDA1 && ft<=fDSS)
93
|| ft==fOPT || ft==fTTL
94
|| (ft>=fAGX && ft<=fCFG);
97
if (ft_base==fSAV || ft_base==fSCR || ft_base==fLOG)
100
if (ft_base==fAGT) { /* Source code */
101
return (ft>=fAGT && ft<=fCMD)
102
|| ft==fTTL || ft==fCFG;
105
fatal("INTERNAL ERROR: Invalid file class.");
111
/*----------------------------------------------------------------------*/
112
/* Misc. utilities */
113
/*----------------------------------------------------------------------*/
115
char *assemble_filename(const char *path, const char *root,
118
int len1, len2, len3;
122
if (path!=NULL) len1=strlen(path);
123
if (root!=NULL) len2=strlen(root);
124
if (ext!=NULL) len3=strlen(ext);
125
name=rmalloc(len1+len2+len3+1);
126
if (path!=NULL) memcpy(name,path,len1);
128
if (ext!=NULL) memcpy(name+len1,ext,len3);
129
if (root!=NULL) memcpy(name+len1+len3,root,len2);
131
if (root!=NULL) memcpy(name+len1,root,len2);
132
if (ext!=NULL) memcpy(name+len1+len2,ext,len3);
134
name[len1+len2+len3]=0;
138
/* This works for binary files; we don't care about non-binary
139
files since this only used to search for game files. */
140
static rbool file_exist(const char *fname)
144
if (f==NULL) return 0;
151
/* This checks to see if c matches any of the characters in matchset */
152
static rbool smatch(char c, const char *matchset)
154
for(;*matchset!=0;matchset++)
155
if (*matchset==c) return 1;
161
/*----------------------------------------------------------------------*/
162
/* Taking Apart the Filename */
163
/*----------------------------------------------------------------------*/
165
static int find_path_sep(const char *name)
171
for(i=strlen(name)-1;i>=0;i--)
172
if (smatch(name[i],path_sep)) break;
177
/* Checks to see if the filename (which must be path-free)
178
has an extension. Returns the length of the extensions
179
and writes the extension type in pft */
180
static int search_for_ext(const char *name, filetype base_ft,
188
if (len==0) return 0;
189
for(t=fNONE+1;t<=fSTD;t++)
190
if (compat_ext(t,base_ft)) {
191
xlen=strlen(extname[t]);
192
if (xlen==0 || xlen>len) continue;
194
if (strncasecmp(name,extname[t],xlen)==0)
196
if (fnamecmp(name+len-xlen,extname[t])==0)
204
/* This is code to make the Unix/etc ports easier to use under
205
tab-completing shells (which often complete "gamename._" or
206
"gamename.ag_" since there are other files in the directory
207
with the same root.) */
209
if (name[len-1]=='.') return 1;
210
if (fnamecmp(name+len-3,".ag")==0) {
211
if (base_ft==fDA1 || base_ft==fAGX) *pft=fAGX;
212
if (base_ft==fAGT) *pft=fAGT;
214
if (fnamecmp(name+len-3,".da")==0) {
215
if (base_ft==fDA1 || base_ft==fAGX) *pft=fDA1;
216
if (base_ft==fAGT) *pft=fAGT;
218
if (*pft!=fNONE) return 3;
224
/* Extract root filename or extension from
225
pathless name, given that the extension is of length extlen. */
226
/* If isext is true, extract the extension. If isext is false,
227
then extrac the root. */
228
static char *extract_piece(const char *name, int extlen, rbool isext)
232
rbool first; /* If true, extract from beginning; if false, extract
235
len=strlen(name)-extlen;
239
tmp=len; len=xlen; xlen=tmp;
241
if (len==0) return NULL;
242
root=rmalloc((len+1)*sizeof(char));
249
memcpy(root,name,len);
252
memcpy(root,name+xlen,len);
259
/* This returns true if "path" is absolute, false otherwise.
260
This is _very_ platform dependent. */
261
static rbool absolute_path(char *path)
264
return pathtest(path);
270
/*----------------------------------------------------------------------*/
271
/* Basic routines for dealing with file contexts */
272
/*----------------------------------------------------------------------*/
274
#define FC(x) ((file_context_rec*)(x))
276
/* formal_name is used to get filenames for diagnostic messages, etc. */
277
char *formal_name(fc_type fc, filetype ft)
279
if (FC(fc)->special) return FC(fc)->gamename;
281
return rstrdup(FC(fc)->shortname);
283
return rstrdup(AGTpSTD);
284
return assemble_filename("",FC(fc)->shortname,extname[ft]);
288
static rbool test_file(const char *path, const char *root, const char *ext)
293
name=assemble_filename(path,root,ext);
294
tmp=file_exist(name);
299
/* This does a path search for the game files. */
300
static void fix_path(file_context_rec *fc)
305
if (gamepath==NULL) return;
306
for(ppath=gamepath;*ppath!=NULL;ppath++)
307
if (test_file(*ppath,fc->shortname,fc->ext)
308
|| test_file(*ppath,fc->shortname,pAGX)
309
|| test_file(*ppath,fc->shortname,DA1))
311
fc->path=rstrdup(*ppath);
318
/* This creates a new file context based on gamename. */
319
/* ft indicates the rough use it will be put towards:
320
ft=fNONE indicates it's the first pass read, before PATH has been
321
read in, and so the fc shouldn't be filled out until
322
fix_file_context() is called.
323
ft=pDA1 indicates that name refers to the game files.
324
ft=pAGX indicates the name of the AGX file to be written to.
325
ft=pSAV,pLOG,pSCR all indicate that name corresponds to the
326
related type of file. */
327
fc_type init_file_context(const char *name,filetype ft)
329
file_context_rec *fc;
330
int p, x; /* Path and extension markers */
332
fc=rmalloc(sizeof(file_context_rec));
336
if (name[0]=='|') { /* Output pipe */
342
fc->gamename=rstrdup(name);
345
x=strlen(fc->gamename);
346
if (fc->gamename[x-1]=='|') { /* Input pipe */
351
fc->path=fc->shortname=fc->ext=NULL;
356
p=find_path_sep(fc->gamename);
360
fc->path=rmalloc((p+2)*sizeof(char));
361
memcpy(fc->path,fc->gamename,p+1);
364
x=search_for_ext(fc->gamename+p+1,ft,&fc->ft);
365
fc->shortname=extract_piece(fc->gamename+p+1,x,0);
366
fc->ext=extract_piece(fc->gamename+p+1,x,1);
369
if (fc->path==NULL && ft==fDA1)
376
void fix_file_context(fc_type fc,filetype ft)
379
if (FC(fc)->path==NULL && ft==fDA1)
385
/* This creates new file contexts from old. */
386
/* This is used to create save/log/script filenames from the game name,
387
and to create include files in the same directory as the source file. */
388
fc_type convert_file_context(fc_type fc,filetype ft,const char *name)
390
file_context_rec *nfc;
391
rbool local_ftype; /* Indicates file should be in working directory,
392
not game directory. */
394
local_ftype=(ft==fSAV || ft==fSCR || ft==fLOG );
395
if (BATCH_MODE || make_test) local_ftype=0;
398
nfc=rmalloc(sizeof(file_context_rec));
401
nfc->shortname=rstrdup(fc->shortname);
406
nfc=init_file_context(name,ft);
409
/* If path already defined, then combine paths. */
410
if (!local_ftype && nfc->path!=NULL && !absolute_path(nfc->path)) {
413
newpath=assemble_filename(fc->path,nfc->path,"");
418
/* scripts, save-games and logs should go in the working directory,
419
not the game directory, so leave nfc->path equal to NULL for them. */
420
if (!local_ftype && nfc->path==NULL)
421
nfc->path=rstrdup(fc->path); /* Put files in game directory */
425
void release_file_context(fc_type *pfc)
427
file_context_rec *fc;
431
rfree(fc->shortname);
437
/*----------------------------------------------------------------------*/
438
/* Routines for Finding Files */
439
/*----------------------------------------------------------------------*/
442
/* This requires that no two sav/scr/log files be open at the same time. */
443
static int pipecnt=0;
444
static FILE *pipelist[6];
446
static genfile try_open_pipe(fc_type fc, filetype ft, rbool rw)
451
if (ft!=fSAV && ft!=fSCR && ft!=fLOG) return NULL;
452
if (rw && fc->special!=1) return NULL;
453
if (!rw && fc->special!=2) return NULL;
454
if (pipecnt>=6) return NULL;
456
f=popen(fc->gamename,rw ? "w" : "r"); /* Need to indicate this is a pipe */
457
pipelist[pipecnt++]=f;
463
static genfile try_open_file(const char *path, const char *root,
464
const char *ext, const char *how,
470
name=assemble_filename(path,root,ext);
474
if (f==NULL && !nofix) { /* Try uppercasing it. */
476
for(s=name;*s!=0;s++)
482
* Try converting just the file name extension to uppercase. This
483
* helps us to handle cases where AGT games are unzipped without
484
* filename case conversions - in these cases, the files will carry
485
* extensions .TTL, .DA1, and so on, whereas we'll be looking for
486
* files with extensions .ttl, .da1, etc. This is one part of
487
* being file name case insensitive - the other part is to define
488
* fnamecmp as strcasecmp, which we do in config.h.
490
if (f == NULL && !nofix && ext != NULL) {
494
/* Free the existing name. */
497
/* Convert the extension to uppercase. */
498
uc_ext = rmalloc (strlen (ext) + 1);
499
for (i = 0; i < strlen (ext); i++)
500
uc_ext[i] = toupper (ext[i]);
501
uc_ext[strlen (ext)] = '\0';
503
/* Form a new filename and try to open that one. */
504
name = assemble_filename (path, root, uc_ext);
506
f = fopen (name, how);
515
static genfile findread(file_context_rec *fc, filetype ft)
522
if (fc->special) { /* It's a pipe */
523
f=try_open_pipe(fc,ft,0);
528
f=try_open_file(fc->path,AGTpSTD,"",filetype_info(ft,0),0);
531
if (ft==fAGX || ft==fNONE) /* Try opening w/o added extension */
532
f=try_open_file(fc->path,fc->shortname,fc->ext,filetype_info(ft,0),0);
534
f=try_open_file(fc->path,fc->shortname,extname[ft],filetype_info(ft,0),0);
539
/*----------------------------------------------------------------------*/
540
/* File IO Routines */
541
/*----------------------------------------------------------------------*/
543
genfile readopen(fc_type fc, filetype ft, char **errstr)
552
if (errno==0 && fc->special) { /* Bad pipe type */
553
*errstr=rstrdup("Invalid pipe request.");
558
t=formal_name(fc,ft);
559
*errstr=rmalloc(30+strlen(t)+strlen(s));
560
sprintf(*errstr,"Cannot open file %s: %s.",t,s);
565
rbool fileexist(fc_type fc, filetype ft)
569
if (fc->special) return 0;
570
f=try_open_file(fc->path,fc->shortname,extname[ft],filetype_info(ft,0),1);
571
if (f!=NULL) { /* File already exists */
579
genfile writeopen(fc_type fc, filetype ft,
580
file_id_type *pfileid, char **errstr)
589
if (fc->special) { /* It's a pipe */
590
f=try_open_pipe(fc,ft,1);
591
if (f==NULL && errno==0) {
592
*errstr=rstrdup("Invalid pipe request.");
595
if (f==NULL) /* For error messages */
596
name=rstrdup(fc->gamename);
600
name=assemble_filename(FC(fc)->path,FC(fc)->shortname,extname[ft]);
601
f=fopen(name,filetype_info(ft,1));
606
*errstr=rmalloc(30+strlen(name)+strlen(s));
607
sprintf(*errstr,"Cannot open file %s: %s.",name,s);
612
*pfileid=(void*)name;
617
rbool filevalid(genfile f, filetype ft)
624
void binseek(genfile f, long offset)
629
if (lseek(fileno(f),offset,SEEK_SET)==-1)
631
if (fseek(f,offset,SEEK_SET)!=0)
633
fatal(strerror(errno));
637
/* This returns the number of bytes read, or 0 if there was an error. */
638
long varread(genfile f, void *buff, long recsize, long recnum, char **errstr)
646
num=(unsigned int)read(fileno(f),buff,recsize*recnum);
647
if (num==(unsigned int)-1)
649
num=read(fileno(f),buff,recsize*recnum);
653
*errstr=rstrdup(strerror(errno));
658
num=fread(buff,recsize,recnum,f);
659
if (num!=recnum && errno!=0)
660
*errstr=rstrdup(strerror(errno));
666
rbool binread(genfile f, void *buff, long recsize, long recnum, char **errstr)
670
num=varread(f,buff,recsize,recnum,errstr);
671
if (num<recsize*recnum && *errstr==NULL)
672
*errstr=rstrdup("Unexpected end of file.");
673
return (*errstr==NULL);
677
rbool binwrite(genfile f, void *buff, long recsize, long recnum, rbool ferr)
681
if (write(fileno(f),buff,recsize*recnum)==-1)
683
if (fwrite(buff,recsize,recnum,f)!=recnum)
686
if (ferr) fatal(strerror(errno));
693
static rbool closepipe(genfile f)
696
for(i=0;i<pipecnt;i++)
697
if (pipelist[i]==f) {
699
for(;i<pipecnt-1;i++)
700
pipelist[i]=pipelist[i+1];
708
void readclose(genfile f)
712
if (closepipe(f)) return;
715
fatal(strerror(errno));
718
void writeclose(genfile f, file_id_type fileid)
723
if (closepipe(f)) return;
728
void binremove(genfile f, file_id_type fileid)
730
assert(f!=NULL); assert(fileid!=NULL);
732
remove((char*)fileid);
736
long binsize(genfile f)
737
/* Returns the size of a binary file */
747
pos=lseek(fd,0,SEEK_CUR);
748
leng=lseek(fd,0,SEEK_END);
749
lseek(fd,pos,SEEK_SET);
755
fseek(f,pos,SEEK_SET);
760
rbool textrewind(genfile f)
768
genfile badfile(filetype ft)