/* Copyright (C) 1989, 2000, 2001 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: zfile.c,v 1.11.2.4.2.2 2003/05/17 14:28:33 ray Exp $ */ /* Non-I/O file operators */ #include "memory_.h" #include "string_.h" #include "ghost.h" #include "gscdefs.h" /* for gx_io_device_table */ #include "gsutil.h" /* for bytes_compare */ #include "gp.h" #include "gsfname.h" #include "gsstruct.h" /* for registering root */ #include "gxalloc.h" /* for streams */ #include "oper.h" #include "dstack.h" /* for systemdict */ #include "estack.h" /* for filenameforall, .execfile */ #include "ialloc.h" #include "ilevel.h" /* %names only work in Level 2 */ #include "interp.h" /* gs_errorinfo_put_string prototype */ #include "iname.h" #include "isave.h" /* for restore */ #include "idict.h" #include "iutil.h" #include "stream.h" #include "strimpl.h" #include "sfilter.h" #include "gxiodev.h" /* must come after stream.h */ /* and before files.h */ #include "files.h" #include "main.h" /* for gs_lib_paths */ #include "store.h" /* Import the IODevice table. */ extern_gx_io_device_table(); /* Import the dtype of the stdio IODevices. */ extern const char iodev_dtype_stdio[]; /* Forward references: file name parsing. */ private int parse_file_name(P3(const ref * op, gs_parsed_file_name_t * pfn, bool safemode)); private int parse_real_file_name(P4(const ref * op, gs_parsed_file_name_t * pfn, gs_memory_t *mem, client_name_t cname)); private int parse_file_access_string(P2(const ref *op, char file_access[4])); /* Forward references: other. */ private int execfile_finish(P1(i_ctx_t *)); private int execfile_cleanup(P1(i_ctx_t *)); private int zopen_file(P5(i_ctx_t *, const gs_parsed_file_name_t *pfn, const char *file_access, stream **ps, gs_memory_t *mem)); private iodev_proc_open_file(iodev_os_open_file); private void file_init_stream(P5(stream *s, FILE *file, const char *fmode, byte *buffer, uint buffer_size)); /* * Since there can be many file objects referring to the same file/stream, * we can't simply free a stream when we close it. On the other hand, * we don't want freed streams to clutter up memory needlessly. * Our solution is to retain the freed streams, and reuse them. * To prevent an old file object from being able to access a reused stream, * we keep a serial number in each stream, and check it against a serial * number stored in the file object (as the "size"); when we close a file, * we increment its serial number. If the serial number ever overflows, * we leave it at zero, and do not reuse the stream. * (This will never happen.) * * Storage management for this scheme is a little tricky. We maintain an * invariant that says that a stream opened at a given save level always * uses a stream structure allocated at that level. By doing this, we don't * need to keep track separately of streams open at a level vs. streams * allocated at a level. To make this interact properly with save and * restore, we maintain a list of all streams allocated at this level, both * open and closed. We store this list in the allocator: this is a hack, * but it simplifies bookkeeping (in particular, it guarantees the list is * restored properly by a restore). * * We want to close streams freed by restore and by garbage collection. We * use the finalization procedure for this. For restore, we don't have to * do anything special to make this happen. For garbage collection, we do * something more drastic: we simply clear the list of known streams (at all * save levels). Any streams open at the time of garbage collection will no * longer participate in the list of known streams, but this does no harm; * it simply means that they won't get reused, and can only be reclaimed by * a future garbage collection or restore. */ /* * Define the default stream buffer sizes. For file streams, * this is arbitrary, since the C library or operating system * does its own buffering in addition. * However, a buffer size of at least 2K bytes is necessary to prevent * JPEG decompression from running very slow. When less than 2K, an * intermediate filter is installed that transfers 1 byte at a time * causing many aborted roundtrips through the JPEG filter code. */ #define DEFAULT_BUFFER_SIZE 2048 const uint file_default_buffer_size = DEFAULT_BUFFER_SIZE; /* An invalid file object */ private stream invalid_file_stream; stream *const invalid_file_entry = &invalid_file_stream; /* Initialize the file table */ private int zfile_init(i_ctx_t *i_ctx_p) { /* Create and initialize an invalid (closed) stream. */ /* Initialize the stream for the sake of the GC, */ /* and so it can act as an empty input stream. */ stream *const s = &invalid_file_stream; s_init(s, NULL); sread_string(s, NULL, 0); s->next = s->prev = 0; s_init_no_id(s); return 0; } /* Make an invalid file object. */ void make_invalid_file(ref * fp) { make_file(fp, avm_invalid_file_entry, ~0, invalid_file_entry); } /* Check a file name for permission by stringmatch on one of the */ /* strings of the permitgroup array */ private int check_file_permissions(i_ctx_t *i_ctx_p, const char *fname, int len, const char *permitgroup) { long i; ref *permitlist = NULL; /* an empty string (first character == 0) if '\' character is */ /* recognized as a file name separator as on DOS & Windows */ bool use_windows_pathsep = *gp_file_name_concat_string("\\", 1) == '\0'; bool fname_bare = !gp_pathstring_not_bare(fname, len); const char *sep_string = NULL; int cwd_len = 0, sep_len = 0; if (dict_find_string(&(i_ctx_p->userparams), permitgroup, &permitlist) <= 0) return 0; /* if Permissions not found, just allow access */ if (fname_bare) { cwd_len = strlen(gp_current_directory_name); sep_string = gp_file_name_concat_string(gp_current_directory_name, cwd_len); sep_len = strlen(sep_string); } for (i=0; iLockFilePermissions && gp_file_name_references_parent(fname, len) && permitstring.value.bytes[0] != '*') continue; /* disregard this match and keep trying */ else return 0; /* success */ } /* fname is a bare name meaning it will default to the current * directory. Check to see if the current permitted path is the * platform's current directory, and if so, succeed. */ if (fname_bare && (r_size(&permitstring) >= cwd_len + sep_len + 1) && (bytes_compare(permitstring.value.bytes, cwd_len, (const byte *)gp_current_directory_name, cwd_len) == 0) && (bytes_compare(permitstring.value.bytes+cwd_len, sep_len, (const byte *)sep_string, sep_len) == 0) && (permitstring.value.bytes[cwd_len+sep_len] == '*')) return 0; /* permitstring was CWD + sep + * */ } /* not found */ return e_invalidfileaccess; } /* file */ private int zfile(i_ctx_t *i_ctx_p) { os_ptr op = osp; char file_access[4]; gs_parsed_file_name_t pname; int code = parse_file_access_string(op, file_access); stream *s; if (code < 0) return code; code = parse_file_name(op - 1, &pname, i_ctx_p->LockFilePermissions); if (code < 0) return code; /* * HACK: temporarily patch the current context pointer into the * state pointer for stdio-related devices. See ziodev.c for * more information. */ if (pname.iodev && pname.iodev->dtype == iodev_dtype_stdio) { bool statement = (strcmp(pname.iodev->dname, "%statementedit%") == 0); bool lineedit = (strcmp(pname.iodev->dname, "%lineedit%") == 0); if (pname.fname) return_error(e_invalidfileaccess); if (statement || lineedit) { /* These need special code to support callouts */ gx_io_device *indev = gs_findiodevice((const byte *)"%stdin", 6); stream *ins; if (strcmp(file_access, "r")) return_error(e_invalidfileaccess); indev->state = i_ctx_p; code = (indev->procs.open_device)(indev, file_access, &ins, imemory); indev->state = 0; if (code < 0) return code; check_ostack(2); push(2); make_stream_file(op - 3, ins, file_access); make_bool(op-2, statement); make_int(op-1, 0); make_string(op, icurrent_space, 0, NULL); return zfilelineedit(i_ctx_p); } pname.iodev->state = i_ctx_p; code = (*pname.iodev->procs.open_device)(pname.iodev, file_access, &s, imemory); pname.iodev->state = NULL; } else { if (pname.iodev == NULL) pname.iodev = iodev_default; code = zopen_file(i_ctx_p, &pname, file_access, &s, imemory); } if (code < 0) return code; code = ssetfilename(s, op[-1].value.const_bytes, r_size(op - 1)); if (code < 0) { sclose(s); return_error(e_VMerror); } make_stream_file(op - 1, s, file_access); pop(1); return code; } /* * Files created with .tempfile permit some operations even if the * temp directory is not explicitly named on the PermitFile... path * The names 'SAFETY' and 'tempfiles' are defined by gs_init.ps */ private bool file_is_tempfile(i_ctx_t *i_ctx_p, const ref *op) { ref *SAFETY; ref *tempfiles; ref kname; if (dict_find_string(systemdict, "SAFETY", &SAFETY) <= 0 || dict_find_string(SAFETY, "tempfiles", &tempfiles) <= 0) return false; if (name_ref(op->value.bytes, r_size(op), &kname, -1) < 0 || dict_find(tempfiles, &kname, &SAFETY) <= 0) return false; return true; } /* ------ Level 2 extensions ------ */ /* deletefile - */ private int zdeletefile(i_ctx_t *i_ctx_p) { os_ptr op = osp; gs_parsed_file_name_t pname; int code = parse_real_file_name(op, &pname, imemory, "deletefile"); if (code < 0) return code; if (pname.iodev == iodev_default) { if ((code = check_file_permissions(i_ctx_p, pname.fname, pname.len, "PermitFileControl")) < 0 && !file_is_tempfile(i_ctx_p, op)) { return code; } } code = (*pname.iodev->procs.delete_file)(pname.iodev, pname.fname); gs_free_file_name(&pname, "deletefile"); if (code < 0) return code; pop(1); return 0; } /*