SA-MP/server/amx/amxfile.c

706 lines
19 KiB
C
Raw Permalink Normal View History

2024-03-12 23:39:27 +08:00
/* Text file I/O module for the Pawn Abstract Machine
*
* Copyright (c) ITB CompuPhase, 2003-2005
*
* This software is provided "as-is", without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in
* a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* Version: $Id: amxfile.c 3363 2005-07-23 09:03:29Z thiadmer $
*/
#if defined _UNICODE || defined __UNICODE__ || defined UNICODE
# if !defined UNICODE /* for Windows */
# define UNICODE
# endif
# if !defined _UNICODE /* for C library */
# define _UNICODE
# endif
#endif
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined __MSDOS__
#include <io.h>
#include <malloc.h>
#endif
#if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined _Windows
#include <windows.h>
#endif
#include "osdefs.h"
#include "amx.h"
#if !defined AMXFILE_VAR
#define AMXFILE_VAR "AMXFILE"
#elif AMXFILE_VAR==""
#undef AMXFILE_VAR
#endif
#if defined _UNICODE
# include <tchar.h>
#elif !defined __T
typedef char TCHAR;
# define __T(string) string
# define _tfopen fopen
# define _tgetenv getenv
# define _tfputs fputs
# define _tcscat strcat
# define _tcschr strchr
# define _tcscpy strcpy
# define _tcsdup strdup
# define _tcslen strlen
# define _tcspbrk strpbrk
# define _tcsrchr strrchr
#endif
#if !defined UNUSED_PARAM
#define UNUSED_PARAM(p) ((void)(p))
#endif
enum filemode {
io_read, /* file must exist */
io_write, /* creates a new file */
io_readwrite, /* file must exist */
io_append, /* file must exist, opened for writing only and seek to the end */
};
enum seek_whence {
seek_start,
seek_current,
seek_end,
};
/* This function only stores unpacked strings. UTF-8 is used for
* Unicode, and packed strings can only store 7-bit and 8-bit
* character sets (ASCII, Latin-1).
*/
static size_t fgets_cell(FILE *fp,cell *string,size_t max,int utf8mode)
{
size_t index;
fpos_t pos;
cell c;
int follow,lastcr;
cell lowmark;
assert(sizeof(cell)>=4);
assert(fp!=NULL);
assert(string!=NULL);
if (max<=0)
return 0;
/* get the position, in case we have to back up */
fgetpos(fp, &pos);
index=0;
follow=0;
lowmark=0;
lastcr=0;
for ( ;; ) {
assert(index<max);
if (index==max-1)
break; /* string fully filled */
if ((c=fgetc(fp))==EOF) {
if (!utf8mode || follow==0)
break; /* no more characters */
/* If an EOF happened halfway an UTF-8 code, the string cannot be
* UTF-8 mode, and we must restart.
*/
index=0;
fsetpos(fp, &pos);
continue;
} /* if */
/* 8-bit characters are unsigned */
if (c<0)
c=-c;
if (utf8mode) {
if (follow>0 && (c & 0xc0)==0x80) {
/* leader code is active, combine with earlier code */
string[index]=(string[index] << 6) | ((unsigned char)c & 0x3f);
if (--follow==0) {
/* encoding a character in more bytes than is strictly needed,
* is not really valid UTF-8; we are strict here to increase
* the chance of heuristic dectection of non-UTF-8 text
* (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid)
*/
if (string[index]<lowmark)
utf8mode=0;
/* the code positions 0xd800--0xdfff and 0xfffe & 0xffff do not
* exist in UCS-4 (and hence, they do not exist in Unicode)
*/
if (string[index]>=0xd800 && string[index]<=0xdfff
|| string[index]==0xfffe || string[index]==0xffff)
utf8mode=0;
index++;
} /* if */
} else if (follow==0 && (c & 0x80)==0x80) {
/* UTF-8 leader code */
if ((c & 0xe0)==0xc0) {
/* 110xxxxx 10xxxxxx */
follow=1;
lowmark=0x80;
string[index]=c & 0x1f;
} else if ((c & 0xf0)==0xe0) {
/* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
follow=2;
lowmark=0x800;
string[index]=c & 0x0f;
} else if ((c & 0xf8)==0xf0) {
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
follow=3;
lowmark=0x10000;
string[index]=c & 0x07;
} else if ((c & 0xfc)==0xf8) {
/* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
follow=4;
lowmark=0x200000;
string[index]=c & 0x03;
} else if ((c & 0xfe)==0xfc) {
/* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31 bits) */
follow=5;
lowmark=0x4000000;
string[index]=c & 0x01;
} else {
/* this is invalid UTF-8 */
utf8mode=0;
} /* if */
} else if (follow==0 && (c & 0x80)==0x00) {
/* 0xxxxxxx (US-ASCII) */
string[index++]=c;
if (c==__T('\n'))
break; /* read newline, done */
} else {
/* this is invalid UTF-8 */
utf8mode=0;
} /* if */
if (!utf8mode) {
/* UTF-8 mode was switched just off, which means that non-conforming
* UTF-8 codes were found, which means in turn that the string is
* probably not intended as UTF-8; start over again
*/
index=0;
fsetpos(fp, &pos);
} /* if */
} else {
string[index++]=c;
if (c==__T('\n')) {
break; /* read newline, done */
} else if (lastcr) {
ungetc(c,fp); /* carriage return was read, no newline follows */
break;
} /* if */
lastcr=(c==__T('\r'));
} /* if */
} /* for */
assert(index<max);
string[index]=__T('\0');
return index;
}
static size_t fputs_cell(FILE *fp,cell *string,int utf8mode)
{
size_t count=0;
assert(sizeof(cell)>=4);
assert(fp!=NULL);
assert(string!=NULL);
while (*string!=0) {
if (utf8mode) {
cell c=*string;
if (c<0x80) {
/* 0xxxxxxx */
fputc((unsigned char)c,fp);
} else if (c<0x800) {
/* 110xxxxx 10xxxxxx */
fputc((unsigned char)((c>>6) & 0x1f | 0xc0),fp);
fputc((unsigned char)(c & 0x3f | 0x80),fp);
} else if (c<0x10000) {
/* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
fputc((unsigned char)((c>>12) & 0x0f | 0xe0),fp);
fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp);
fputc((unsigned char)(c & 0x3f | 0x80),fp);
} else if (c<0x200000) {
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
fputc((unsigned char)((c>>18) & 0x07 | 0xf0),fp);
fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp);
fputc((unsigned char)(c & 0x3f | 0x80),fp);
} else if (c<0x4000000) {
/* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
fputc((unsigned char)((c>>24) & 0x03 | 0xf8),fp);
fputc((unsigned char)((c>>18) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp);
fputc((unsigned char)(c & 0x3f | 0x80),fp);
} else {
/* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31 bits) */
fputc((unsigned char)((c>>30) & 0x01 | 0xfc),fp);
fputc((unsigned char)((c>>24) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>18) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>12) & 0x3f | 0x80),fp);
fputc((unsigned char)((c>>6) & 0x3f | 0x80),fp);
fputc((unsigned char)(c & 0x3f | 0x80),fp);
} /* if */
} else {
/* not UTF-8 mode */
fputc((unsigned char)*string,fp);
} /* if */
string++;
count++;
} /* while */
return count;
}
static size_t fgets_char(FILE *fp, char *string, size_t max)
{
size_t index;
int c,lastcr;
index=0;
lastcr=0;
for ( ;; ) {
assert(index<max);
if (index==max-1)
break; /* string fully filled */
if ((c=fgetc(fp))==EOF)
break; /* no more characters */
string[index++]=(char)c;
if (c==__T('\n')) {
break; /* read newline, done */
} else if (lastcr) {
ungetc(c,fp); /* carriage return was read, no newline follows */
break;
} /* if */
lastcr=(c==__T('\r'));
} /* for */
assert(index<max);
string[index]=__T('\0');
return index;
}
#if defined __WIN32__ || defined _WIN32 || defined WIN32
#if defined _UNICODE
wchar_t *_wgetenv(wchar_t *name)
{
static wchar_t buffer[_MAX_PATH];
buffer[0]=L'\0';
GetEnvironmentVariable(name,buffer,sizeof buffer/sizeof(wchar_t));
return buffer[0]!=L'\0' ? buffer : NULL;
}
#else
2024-06-23 22:13:14 +08:00
char *amx_getenv(const char *name)
2024-03-12 23:39:27 +08:00
{
static char buffer[_MAX_PATH];
buffer[0]='\0';
GetEnvironmentVariable(name,buffer,sizeof buffer);
return buffer[0]!='\0' ? buffer : NULL;
}
#endif
2024-06-23 22:13:14 +08:00
#else
#define amx_getenv getenv
2024-03-12 23:39:27 +08:00
#endif
static char *completename(TCHAR *dest, TCHAR *src, size_t size)
{
#if defined AMXFILE_VAR
TCHAR *prefix,*ptr;
size_t len;
2024-06-23 22:13:14 +08:00
2024-03-12 23:39:27 +08:00
/* only files below a specific path are accessible */
2024-06-23 22:13:14 +08:00
prefix=amx_getenv(AMXFILE_VAR);
2024-03-12 23:39:27 +08:00
/* if no specific path for files is present, use the "temporary" path */
if (prefix==NULL)
2024-06-23 22:13:14 +08:00
prefix=amx_getenv(__T("tmp")); /* common under Windows and Unix */
2024-03-12 23:39:27 +08:00
if (prefix==NULL)
2024-06-23 22:13:14 +08:00
prefix=amx_getenv(__T("temp")); /* common under Windows */
2024-03-12 23:39:27 +08:00
if (prefix==NULL)
2024-06-23 22:13:14 +08:00
prefix=amx_getenv(__T("tmpdir")); /* common under Unix */
2024-03-12 23:39:27 +08:00
/* if no path for files is defined, and no temporary directory exists,
* fail the function; this is for security reasons.
*/
if (prefix==NULL)
return NULL;
if (_tcslen(prefix)+1>=size) /* +1 because directory separator is appended */
return NULL;
_tcscpy(dest,prefix);
/* append a directory separator (if not already present) */
len=_tcslen(dest);
if (len==0)
return NULL; /* empty start directory is not allowed */
if (dest[len-1]!=__T(DIRSEP_CHAR) && dest[len-1]!=__T('/') && len+1<size) {
dest[len]=__T(DIRSEP_CHAR);
dest[len+1]=__T('\0');
} /* if */
assert(_tcslen(dest)<size);
/* for DOS/Windows and Unix/Linux, skip everyting up to a comma, because
* this is used to indicate a protocol (e.g. file://C:/myfile.txt)
*/
#if DIRSEP_CHAR!=':'
if ((ptr=_tcsrchr(src,__T(':')))!=NULL) {
src=ptr+1; /* skip protocol/drive and colon */
/* a "drive" specifier is sometimes ended with a vertical bar instead
* of a colon in URL specifications
*/
if ((ptr=_tcschr(src,__T('|')))!=NULL)
src=ptr+1; /* skip drive and vertical bar */
while (src[0]==__T(DIRSEP_CHAR) || src[0]==__T('/'))
src++; /* skip slashes behind the protocol/drive letter */
} /* if */
#endif
/* skip an initial backslash or a drive specifier in the source */
if ((src[0]==__T(DIRSEP_CHAR) || src[0]==__T('/')) && (src[1]==__T(DIRSEP_CHAR) || src[1]==__T('/'))) {
/* UNC path */
char separators[]={__T(DIRSEP_CHAR),__T('/'),__T('\0')};
src+=2;
ptr=_tcspbrk(src,separators);
if (ptr!=NULL)
src=ptr+1;
} else if (src[0]==__T(DIRSEP_CHAR) || src[0]==__T('/')) {
/* simple path starting from the root directory */
src++;
} /* if */
/* disallow any "../" specifications in the source path
* (the check below should be stricter, but directory names with
* trailing periods are rare anyway)
*/
for (ptr=src; *ptr!=__T('\0'); ptr++)
if (ptr[0]==__T('.') && (ptr[1]==__T(DIRSEP_CHAR) || ptr[1]==__T('/')))
return NULL; /* path name is not allowed */
/* concatenate the drive letter to the destination path */
if (_tcslen(dest)+_tcslen(src)>=size)
return NULL;
_tcscat(dest,src);
/* change forward slashes into proper directory separators */
#if DIRSEP_CHAR!='/'
while ((ptr=_tcschr(dest,__T('/')))!=NULL)
*ptr=__T(DIRSEP_CHAR);
#endif
return dest;
#else
if (_tcslen(src)>=size)
return NULL;
_tcscpy(dest,src);
/* change forward slashes into proper directory separators */
#if DIRSEP_CHAR!='/'
while ((ptr=_tcschr(dest,__T('/')))!=NULL)
*ptr=__T(DIRSEP_CHAR);
#endif
return dest;
#endif
}
/* File: fopen(const name[], filemode: mode) */
static cell AMX_NATIVE_CALL n_fopen(AMX *amx, cell *params)
{
TCHAR *attrib,*altattrib;
TCHAR *name,fullname[_MAX_PATH];
FILE *f = NULL;
altattrib=NULL;
switch (params[2] & 0x7fff) {
case io_read:
attrib=__T("rb");
break;
case io_write:
attrib=__T("wb");
break;
case io_readwrite:
attrib=__T("r+b");
altattrib=__T("w+b");
break;
case io_append:
attrib=__T("ab");
break;
default:
return 0;
} /* switch */
/* get the filename */
amx_StrParam(amx,params[1],name);
if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL) {
f=_tfopen(fullname,attrib);
if (f==NULL && altattrib!=NULL)
f=_tfopen(fullname,altattrib);
} /* if */
return (cell)f;
}
/* fclose(File: handle) */
static cell AMX_NATIVE_CALL n_fclose(AMX *amx, cell *params)
{
UNUSED_PARAM(amx);
return fclose((FILE*)params[1]) == 0;
}
/* fwrite(File: handle, const string[]) */
static cell AMX_NATIVE_CALL n_fwrite(AMX *amx, cell *params)
{
int r = 0;
cell *cptr;
char *str;
int len;
amx_GetAddr(amx,params[2],&cptr);
amx_StrLen(cptr,&len);
if (len==0)
return 0;
if ((ucell)*cptr>UNPACKEDMAX) {
/* the string is packed, write it as an ASCII/ANSI string */
if ((str=(char*)alloca(len + 1))!=NULL)
r=_tfputs(str,(FILE*)params[1]);
} else {
/* the string is unpacked, write it as UTF-8 */
r=fputs_cell((FILE*)params[1],cptr,1);
} /* if */
return r;
}
/* fread(File: handle, string[], size=sizeof string, bool:pack=false) */
static cell AMX_NATIVE_CALL n_fread(AMX *amx, cell *params)
{
int chars,max;
char *str;
cell *cptr;
max=(int)params[3];
if (max<=0)
return 0;
if (params[4])
max*=sizeof(cell);
amx_GetAddr(amx,params[2],&cptr);
str=(char *)alloca(max);
if (str==NULL || cptr==NULL) {
amx_RaiseError(amx, AMX_ERR_NATIVE);
return 0;
} /* if */
if (params[4]) {
/* store as packed string, read an ASCII/ANSI string */
chars=fgets_char((FILE*)params[1],str,max);
assert(chars<max);
amx_SetString(cptr,str,(int)params[4],0,max);
} else {
/* store and unpacked string, interpret UTF-8 */
chars=fgets_cell((FILE*)params[1],cptr,max,1);
} /* if */
assert(chars<max);
return chars;
}
/* fputchar(File: handle, value, bool:utf8 = true) */
static cell AMX_NATIVE_CALL n_fputchar(AMX *amx, cell *params)
{
size_t result;
UNUSED_PARAM(amx);
if (params[3]) {
cell str[2];
str[0]=params[2];
str[1]=0;
result=fputs_cell((FILE*)params[1],str,1);
} else {
fputc((int)params[2],(FILE*)params[1]);
} /* if */
2024-06-23 22:12:43 +08:00
//assert(result==0 || result==1);
2024-03-12 23:39:27 +08:00
return result;
}
/* fgetchar(File: handle, value, bool:utf8 = true) */
static cell AMX_NATIVE_CALL n_fgetchar(AMX *amx, cell *params)
{
cell str[2];
size_t result;
UNUSED_PARAM(amx);
if (params[3]) {
result=fgets_cell((FILE*)params[1],str,2,1);
} else {
str[0]=fgetc((FILE*)params[1]);
result= (str[0]!=EOF);
} /* if */
assert(result==0 || result==1);
if (result==0)
return EOF;
else
return str[0];
}
#if PAWN_CELL_SIZE==16
#define aligncell amx_Align16
#elif PAWN_CELL_SIZE==32
#define aligncell amx_Align32
#elif PAWN_CELL_SIZE==64 && (defined _I64_MAX || defined HAVE_I64)
#define aligncell amx_Align64
#else
#error Unsupported cell size
#endif
/* fblockwrite(File: handle, buffer[], size=sizeof buffer) */
static cell AMX_NATIVE_CALL n_fblockwrite(AMX *amx, cell *params)
{
cell *cptr;
cell count;
amx_GetAddr(amx,params[2],&cptr);
if (cptr!=NULL) {
cell max=params[3];
ucell v;
for (count=0; count<max; count++) {
v=(ucell)*cptr++;
if (fwrite(aligncell(&v),sizeof(cell),1,(FILE*)params[1])!=1)
break; /* write error */
} /* for */
} /* if */
return count;
}
/* fblockread(File: handle, buffer[], size=sizeof buffer) */
static cell AMX_NATIVE_CALL n_fblockread(AMX *amx, cell *params)
{
cell *cptr;
cell count;
amx_GetAddr(amx,params[2],&cptr);
if (cptr!=NULL) {
cell max=params[3];
ucell v;
for (count=0; count<max; count++) {
if (fread(&v,sizeof(cell),1,(FILE*)params[1])!=1)
break; /* write error */
*cptr++=(cell)*aligncell(&v);
} /* for */
} /* if */
return count;
}
/* File: ftemp() */
static cell AMX_NATIVE_CALL n_ftemp(AMX *amx, cell *params)
{
UNUSED_PARAM(amx);
UNUSED_PARAM(params);
return (cell)tmpfile();
}
/* fseek(File: handle, position, seek_whence: whence=seek_start) */
static cell AMX_NATIVE_CALL n_fseek(AMX *amx, cell *params)
{
int whence;
switch (params[3]) {
case seek_start:
whence=SEEK_SET;
break;
case seek_current:
whence=SEEK_CUR;
break;
case seek_end:
whence=SEEK_END;
//if (params[2]>0)
// params[2]=-params[2];
break;
default:
return 0;
} /* switch */
UNUSED_PARAM(amx);
return fseek((FILE*)params[1], params[2], whence);
2024-03-12 23:39:27 +08:00
}
/* bool: fexist(const name[]) */
static cell AMX_NATIVE_CALL n_fexist(AMX *amx, cell *params)
{
int r=1;
TCHAR *name,fullname[_MAX_PATH];
amx_StrParam(amx,params[1],name);
if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL)
r=access(fullname,0);
return r==0;
}
/* bool: fremove(const name[]) */
static cell AMX_NATIVE_CALL n_fremove(AMX *amx, cell *params)
{
int r=1;
TCHAR *name,fullname[_MAX_PATH];
amx_StrParam(amx,params[1],name);
if (name!=NULL && completename(fullname,name,sizeof fullname)!=NULL)
r=remove(fullname);
return r==0;
}
/* flength(File: handle) */
static cell AMX_NATIVE_CALL n_flength(AMX *amx, cell *params)
{
long l,c;
int fn=fileno((FILE*)params[1]);
c=lseek(fn,0,SEEK_CUR); /* save the current position */
l=lseek(fn,0,SEEK_END); /* return the file position at its end */
lseek(fn,c,SEEK_SET); /* restore the file pointer */
UNUSED_PARAM(amx);
return l;
}
#if defined __cplusplus
extern "C"
#endif
AMX_NATIVE_INFO file_Natives[] = {
{ "fopen", n_fopen },
{ "fclose", n_fclose },
{ "fwrite", n_fwrite },
{ "fread", n_fread },
{ "fputchar", n_fputchar },
{ "fgetchar", n_fgetchar },
{ "fblockwrite", n_fblockwrite },
{ "fblockread", n_fblockread },
{ "ftemp", n_ftemp },
{ "fseek", n_fseek },
{ "fexist", n_fexist },
{ "flength", n_flength },
{ "fremove", n_fremove },
{ NULL, NULL } /* terminator */
};
int AMXEXPORT amx_FileInit(AMX *amx)
{
return amx_Register(amx, file_Natives, -1);
}
int AMXEXPORT amx_FileCleanup(AMX *amx)
{
UNUSED_PARAM(amx);
return AMX_ERR_NONE;
}