int CVmNetFile::can_write(VMG_ const char *fname, int sfid) { /* note whether the file already exists */ int existed = !osfacc(fname); /* try opening it (keeping existing contents) or creating it */ osfildef *fp = osfoprwb(fname, OSFTBIN); if (fp != 0) { /* successfully opened it - close it */ osfcls(fp); /* if it didn't already exist, delete it */ if (!existed) osfdel(fname); /* success */ return TRUE; } else { /* couldn't open the file */ return FALSE; } }
/* * Look for a file in the "standard locations": current directory, program * directory, PATH-like environment variables, etc. The actual standard * locations are specific to each platform; the implementation is free to * use whatever conventions are appropriate to the local system. On * systems that have something like Unix environment variables, it might be * desirable to define a TADS-specific variable (TADSPATH, for example) * that provides a list of directories to search for TADS-related files. * * On return, fill in 'buf' with the full filename of the located copy of * the file (if a copy was indeed found), in a format suitable for use with * the osfopxxx() functions; in other words, after this function returns, * the caller should be able to pass the contents of 'buf' to an osfopxxx() * function to open the located file. * * Returns true (non-zero) if a copy of the file was located, false (zero) * if the file could not be found in any of the standard locations. */ int os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) { if (!osfacc(fname)) { memcpy(buf, fname, flen); buf[flen] = 0; return TRUE; } return FALSE; }
/* * Rename a file */ void CVmNetFile::rename_to_local(VMG_ CVmNetFile *newname) { /* if the new name isn't local, this isn't supported */ if (newname->is_net_file()) err_throw(VMERR_RENAME_FILE); /* if the destination file already exists, it's an error */ if (!osfacc(newname->lclfname)) err_throw(VMERR_RENAME_FILE); /* do the rename */ if (!os_rename_file(lclfname, newname->lclfname)) err_throw(VMERR_RENAME_FILE); }
/* * Given a game file argument, determine which engine (TADS 2 or TADS 3) * should be used to run the game. */ int vm_get_game_type(const char *filename, char *actual_fname, size_t actual_fname_len, const char *const *defexts, size_t defext_count) { int good_count; int last_good_ver; size_t i; /* * If the exact filename as given exists, determine the file type * directly from this file without trying any default extensions. */ if (osfacc(filename) == 0) { /* the actual filename is exactly what we were given */ if (actual_fname_len != 0) { /* copy the filename, limiting it to the buffer length */ strncpy(actual_fname, filename, actual_fname_len); actual_fname[actual_fname_len - 1] = '\0'; } /* return the type according to the file's signature */ return vm_get_game_type_for_file(filename); } /* presume we won't find any good files using default extensions */ good_count = 0; /* try each default extension supplied */ for (i = 0 ; i < defext_count ; ++i) { int cur_ver; char cur_fname[OSFNMAX]; /* * build the default filename from the given filename and the * current default suffix */ strcpy(cur_fname, filename); os_defext(cur_fname, defexts[i]); /* get the version for this file */ cur_ver = vm_get_game_type_for_file(cur_fname); /* if it's a valid code, note it and remember it */ if (vm_ggt_is_valid(cur_ver)) { /* it's a valid file - count it */ ++good_count; /* remember its version as the last good file's version */ last_good_ver = cur_ver; /* remember its name as the last good file's name */ if (actual_fname_len != 0) { /* copy the filename, limiting it to the buffer length */ strncpy(actual_fname, cur_fname, actual_fname_len); actual_fname[actual_fname_len - 1] = '\0'; } } } /* * If we had exactly one good match, return it. We will already have * filled in actual_fname with the last good filename, so all we need * to do in this case is return the version ID for the last good file. */ if (good_count == 1) return last_good_ver; /* * If we didn't find any matches, tell the caller there is no match * for the given filename. */ if (good_count == 0) return VM_GGT_NOT_FOUND; /* we found more than one match, so the type is ambiguous */ return VM_GGT_AMBIG; }
/* * Main Entrypoint for command-line invocations. For simplicity, a * normal C main() or equivalent entrypoint can invoke this routine * directly, using the usual argc/argv conventions. * * Returns a status code suitable for use with exit(): OSEXSUCC if we * successfully loaded and ran an executable, OSEXFAIL on failure. If * an error occurs, we'll fill in 'errbuf' with a message describing the * problem. */ int vm_run_image_main(CVmMainClientIfc *clientifc, const char *executable_name, int argc, char **argv, int defext, int test_mode, CVmHostIfc *hostifc) { int curarg; char image_file_name[OSFNMAX]; int stat; const char *script_file; int script_quiet = TRUE; const char *log_file; const char *cmd_log_file; const char *res_dir; int load_from_exe; int show_banner; int found_image; int hide_usage; int usage_err; const char *charset; const char *log_charset; char *saved_state; /* we haven't found an image file yet */ found_image = FALSE; /* presume we'll show usage on error */ hide_usage = FALSE; /* presume there will be no usage error */ usage_err = FALSE; /* presume we won't have any console input/output files */ script_file = 0; log_file = 0; cmd_log_file = 0; /* presume we'll use the default OS character sets */ charset = 0; log_charset = 0; /* presume we won't show the banner */ show_banner = FALSE; /* presume we won't load from the .exe file */ load_from_exe = FALSE; /* check to see if we can load from the .exe file */ { osfildef *fp; /* look for an image file attached to the executable */ fp = os_exeseek(argv[0], "TGAM"); if (fp != 0) { /* close the file */ osfcls(fp); /* note that we want to load from the executable */ load_from_exe = TRUE; } } /* presume we won't restore a saved state file */ saved_state = 0; /* presume we won't have a resource directory specified */ res_dir = 0; /* scan options */ for (curarg = 1 ; curarg < argc && argv[curarg][0] == '-' ; ++curarg) { /* * if the argument is just '-', it means we're explicitly leaving * the image filename blank and skipping to the arguments to the VM * program itself */ if (argv[curarg][1] == '\0') break; /* check the argument */ switch(argv[curarg][1]) { case 'b': if (strcmp(argv[curarg], "-banner") == 0) { /* make a note to show the banner */ show_banner = TRUE; } else goto opt_error; break; case 'c': if (strcmp(argv[curarg], "-cs") == 0) { ++curarg; if (curarg < argc) charset = argv[curarg]; else goto opt_error; } else if (strcmp(argv[curarg], "-csl") == 0) { ++curarg; if (curarg < argc) log_charset = argv[curarg]; else goto opt_error; } else goto opt_error; break; case 'n': if (strcmp(argv[curarg], "-nobanner") == 0) { /* make a note not to show the banner */ show_banner = FALSE; } else goto opt_error; break; case 's': /* file safety level - check the range */ if (argv[curarg][2] < '0' || argv[curarg][2] > '4' || argv[curarg][3] != '\0') { /* invalid level */ goto opt_error; } else { /* set the level in the host application */ hostifc->set_io_safety(argv[curarg][2] - '0'); } break; case 'i': case 'I': /* * read from a script file (little 'i' reads silently, big 'I' * echoes the log as it goes) - the next argument, or the * remainder of this argument, is the filename */ script_quiet = (argv[curarg][1] == 'i'); script_file = get_opt_arg(argc, argv, &curarg, 2); if (script_file == 0) goto opt_error; break; case 'l': /* log output to file */ log_file = get_opt_arg(argc, argv, &curarg, 2); if (log_file == 0) goto opt_error; break; case 'o': /* log commands to file */ cmd_log_file = get_opt_arg(argc, argv, &curarg, 2); if (cmd_log_file == 0) goto opt_error; break; case 'p': /* check what follows */ if (strcmp(argv[curarg], "-plain") == 0) { /* tell the client to set plain ASCII mode */ clientifc->set_plain_mode(); break; } else goto opt_error; break; case 'r': /* get the name of the saved state file to restore */ saved_state = get_opt_arg(argc, argv, &curarg, 2); if (saved_state == 0) goto opt_error; break; case 'R': /* note the resource root directory */ res_dir = get_opt_arg(argc, argv, &curarg, 2); if (res_dir == 0) goto opt_error; break; default: opt_error: /* discard remaining arguments */ curarg = argc; /* note the error */ usage_err = TRUE; break; } } /* * If there was no usage error so far, but we don't have an image * filename argument, try to find the image file some other way. If we * found an image file embedded in the executable, don't even bother * looking for an image-file argument - we can only run the embedded * image in this case. */ if (usage_err) { /* there was a usage error - don't bother looking for an image file */ } else if (!load_from_exe && curarg + 1 <= argc && strcmp(argv[curarg], "-") != 0) { /* the last argument is the image file name */ strcpy(image_file_name, argv[curarg]); found_image = TRUE; /* * If the given filename exists, use it as-is; otherwise, if * we're allowed to add an extension, try applying a default * extension of "t3" (formerly "t3x") to the given name. */ if (defext && osfacc(image_file_name)) { /* the given name doesn't exist - try a default extension */ os_defext(image_file_name, "t3"); /* formerly "t3x" */ } } else { /* * if we're loading from the executable, try using the executable * filename as the image file */ if (load_from_exe && os_get_exe_filename(image_file_name, sizeof(image_file_name), argv[0])) found_image = TRUE; /* * If we still haven't found an image file, try to get the image * file from the saved state file, if one was specified. Don't * attempt this if we're loading the image from the executable, as * we don't want to allow running a different image file in that * case. */ if (!load_from_exe && !found_image && saved_state != 0) { osfildef *save_fp; /* open the saved state file */ save_fp = osfoprb(saved_state, OSFTT3SAV); if (save_fp != 0) { /* get the name of the image file */ if (CVmSaveFile::restore_get_image( save_fp, image_file_name, sizeof(image_file_name)) == 0) { /* we successfully obtained the filename */ found_image = TRUE; } /* close the file */ osfcls(save_fp); } } /* * if we haven't found the image, and the host system provides a * way of asking the user for a filename, try that */ if (!load_from_exe && !found_image) { /* ask the host system for a game name */ switch (hostifc->get_image_name(image_file_name, sizeof(image_file_name))) { case VMHOST_GIN_IGNORED: /* no effect - we have no new information */ break; case VMHOST_GIN_CANCEL: /* * the user cancelled the dialog - we don't have a * filename, but we also don't want to show usage, since * the user chose not to proceed */ hide_usage = TRUE; break; case VMHOST_GIN_ERROR: /* * an error occurred showing the dialog - there's not * much we can do except show the usage message */ break; case VMHOST_GIN_SUCCESS: /* that was successful - we have an image file now */ found_image = TRUE; break; } } } /* * if we don't have an image file name by this point, we can't * proceed - show the usage message and terminate */ if (usage_err || !found_image) { char buf[OSFNMAX + 1024]; /* show the usage message if allowed */ if (load_from_exe && !usage_err) { sprintf(buf, "An error occurred loading the T3 VM program from " "the embedded data file. This application executable " "file might be corrupted.\n"); /* display the message */ clientifc->display_error(0, buf, FALSE); } else if (!hide_usage) { /* build the usage message */ sprintf(buf, "%s\n" "usage: %s [options] %sarguments]\n" "options:\n" " -banner - show the version/copyright banner\n" " -cs xxx - use character set 'xxx' for keyboard " "and display\n" " -csl xxx - use character set 'xxx' for log files\n" " -i file - read command input from file (quiet mode)\n" " -I file - read command input from file (echo mode)\n" " -l file - log all console input/output to file\n" " -o file - log console input to file\n" " -plain - run in plain mode (no cursor positioning, " "colors, etc.)\n" " -r file - restore saved state from file\n" " -R dir - set directory for external resources\n" " -s# - set I/O safety level (# in range 0 to 4 - 0 " "is the least\n" " restrictive, 4 allows no file I/O at all)\n" "\n" "If provided, the optional extra arguments are passed " "to the program's\n" "main entrypoint.\n", T3VM_BANNER_STRING, executable_name, load_from_exe ? "[- " : "<image-file-name> ["); /* display the message */ clientifc->display_error(0, buf, FALSE); } /* return failure */ return OSEXFAIL; } /* * if we're in test mode, replace the first argument to the program * with its root name, so that we don't include any path information * in the argument list */ if (test_mode && curarg <= argc && argv[curarg] != 0) argv[curarg] = os_get_root_name(argv[curarg]); /* run the program */ stat = vm_run_image(clientifc, image_file_name, hostifc, argv + curarg, argc - curarg, script_file, script_quiet, log_file, cmd_log_file, load_from_exe, show_banner, charset, log_charset, saved_state, res_dir); /* return the status code */ return stat; }
int CVmNetFile::exists(VMG_ const char *fname, int sfid) { return !osfacc(fname); }
/* Get the local time zone name, as a zoneinfo database key. Many * modern Unix systems use zoneinfo keys as they native timezone * setting, but there are several different ways of storing this on a * per-process and system-wide basis. We'll try looking for the common * mechanisms. */ int os_get_zoneinfo_key( char *buf, size_t buflen ) { /* First, try the TZ environment variable. This is used on nearly * all Unix-alikes for a per-process timezone setting, although * it will only contain a zoneinfo key in newer versions. There * are several possible formats for specifying a zoneinfo key: * * TZ=/usr/share/zoneinfo/America/Los_Angeles * - A full absolute path name to a tzinfo file. We'll sense * this by looking for "/zoneinfo/" in the string, and if we * find it, we'll return the portion after /zoneinfo/. * * TZ=America/Los_Angeles * - Just the zoneinfo key, without a path. If we find a string * that contains all alphabetics, undersores, and slashes, and * has at least one internal slash but doesn't start with a * slash, we probably have a zoneinfo key. We'll see if we * can find a matching file in the usual zoneinfo database * locations: /etc/zoneinfo, /usr/share/zoneinfo; if we can, * we'll return the key name, otherwise we'll assume this * isn't actually a zoneinfo key but just happens to look like * one in terms of format. * * TZ=:America/Los_Angeles * TZ=:/etc/zoneinfo/America/Los_Angeles * - POSIX systems generally use the ":" prefix to signify that * this is a zoneinfo path rather than the old-style "EST5EDT" * type of self-contained zone description. If we see a colon * prefix with a relative path (properly formed in terms of * its character content), we'll simply assume this is a * zoneinfo key without even checking for an existing file, * since there's not much else it could be. If we see an * absolute path, we'll search it for /zoneinfo/ and return * the portion after this, again without checking for an * existing file. */ const char *tz = getenv("TZ"); if (tz != 0 && tz[0] != '\0') { /* check that the string is formatted like a zoneinfo key */ #define tzcharok(c) (isalpha(c) != 0 || (c) == '/' || (c) == '_') int fmt_ok = TRUE; const char *p; fmt_ok &= (tz[0] == ':' || tzcharok(tz[0])); for (p = tz + 1 ; *p != '\0' ; ++p) fmt_ok &= tzcharok(*p); /* proceed only if it has the right format */ if (fmt_ok) { /* check for a leading ':', per POSIX */ if (tz[0] == ':') { /* yes, we have a leading ':', so it's almost certainly * a zoneinfo key; if it's an absolute path, find the * part after /zoneinfo/ */ if (tz[1] == '/') { /* absolute form - look for /zoneinfo/ */ const char *z = strstr(tz, "/zoneinfo/"); if (z != 0) { /* found it - return the part after /zoneinfo/ */ safe_strcpy(buf, buflen, z + 10); return TRUE; } } else { /* relative path - return as-is minus the colon */ safe_strcpy(buf, buflen, tz + 1); return TRUE; } } else { /* There's no colon, so it *might* be a zoneinfo key. * If it's an absolute path containing /zoneinfo/, it's * a solid bet. If it's a relative path, look to see * if we can find a file in one of the usual zoneinfo * database locations. */ if (tz[0] == '/') { /* absolute path - check for /zoneinfo/ */ const char *z = strstr(tz, "/zoneinfo/"); if (z != 0) { /* found it - return the part after /zoneinfo/ */ safe_strcpy(buf, buflen, z + 10); return TRUE; } } else { /* relative path - look for a tzinfo file in the * usual locations */ static const char *dirs[] = { "/etc/zoneinfo", "/usr/share/zoneinfo", 0 }; const char **dir; for (dir = dirs ; *dir != 0 ; ++dir) { /* build this full path */ char fbuf[OSFNMAX]; os_build_full_path(fbuf, sizeof(fbuf), *dir, tz); /* check for a file at this location */ if (!osfacc(fbuf)) { /* got it - looks like a good zoneinfo key */ safe_strcpy(buf, buflen, tz); return TRUE; } } } } } } /* No luck with TZ, so try the system-wide settings next. * * If a file called /etc/timezone exists, it's usually a one-line * text file containing the zoneinfo key. Read and return its * contents. */ FILE *fp; if ((fp = fopen("/etc/timezone", "r")) != 0) { /* read the one-liner */ char lbuf[256]; int ok = FALSE; if (fgets(lbuf, sizeof(lbuf), fp) != 0) { /* strip any trailing newline */ size_t l = strlen(lbuf); if (l != 0 && lbuf[l-1] == '\n') lbuf[l-1] = '\0'; /* if it's in absolute format, return the part after * /zoneinfo/; otherwise just return the string */ if (lbuf[0] == '/') { /* absoltue path - find /zoneinfo/ */ const char *z = strstr(lbuf, "/zoneinfo/"); if (z != 0) { safe_strcpy(buf, buflen, z + 10); ok = TRUE; } } else { /* relative notation - return it as-is */ safe_strcpy(buf, buflen, lbuf); ok = TRUE; } } /* we're done with the file */ fclose(fp); /* if we got our result, return success */ if (ok) return TRUE; } /* If /etc/sysconfig/clock exists, read it and look for a line * starting with ZONE=. This contains the zoneinfo key. */ if ((fp = fopen("/etc/sysconfig/clock", "r")) != 0) { /* scan the file for ZONE=... */ int ok = FALSE; for (;;) { /* read the next line */ char lbuf[256]; if (fgets(lbuf, sizeof(lbuf), fp) == 0) break; /* skip leading spaces */ const char *p; for (p = lbuf ; isspace(*p) ; ++p) ; /* check for ZONE */ if (memicmp(p, "zone", 4) != 0) continue; /* skip spaces after ZONE */ for (p += 4 ; isspace(*p) ; ++p) ; /* check for '=' */ if (*p != '=') continue; /* skip spaces after the '=' */ for (++p ; isspace(*p) ; ++p) ; /* if it's in absolute form, look for /zoneinfo/ */ if (*p == '/') { const char *z = strstr(p, "/zoneinfo/"); if (z != 0) { safe_strcpy(buf, buflen, z + 10); ok = TRUE; } } else { /* relative notation - it's the zoneinfo key */ safe_strcpy(buf, buflen, p); ok = TRUE; } /* that's our ZONE line, so we're done scanning the file */ break; } /* done with the file */ fclose(fp); /* if we got our result, return success */ if (ok) return TRUE; } /* If /etc/localtime is a symbolic link, the linked file is the * actual zoneinfo file. Resolve the link and return the portion * of the path after "/zoneinfo/". */ #if 0 static const char *elt = "/etc/localtime"; unsigned long mode; char linkbuf[OSFNMAX]; const char *zi; if (osfmode(elt, FALSE, &mode, NULL) && (mode & OSFMODE_LINK) != 0 && os_resolve_symlink(elt, linkbuf, sizeof(linkbuf)) && (zi = strstr(linkbuf, "/zoneinfo/")) != 0) { /* it's a link containing /zoneinfo/, so return the portion * after /zoneinfo/ */ safe_strcpy(buf, buflen, zi + 10); return TRUE; } #endif /* well, we're out of ideas - return failure */ return FALSE; }
/* * Main program entrypoint */ int main(int argc, char **argv) { int curarg; int create; int recurse = TRUE; const char *image_fname; CRcResList *res_list = 0; rcmain_res_op_mode_t op_mode; int exit_stat; int err; MyHostIfc hostifc; char image_buf[OSFNMAX]; /* assume we will operate on an existing file */ create = FALSE; /* start out in add-recursive mode */ op_mode = RCMAIN_RES_OP_MODE_ADD; /* scan options */ for (curarg = 1 ; curarg < argc && argv[curarg][0] == '-' ; ++curarg) { switch(argv[curarg][1]) { case 'c': if (strcmp(argv[curarg], "-create") == 0) create = TRUE; else goto bad_option; break; default: bad_option: /* invalid option - skip all arguments so we go to usage */ curarg = argc; break; } } /* if there's nothing left, show usage */ if (curarg >= argc) { show_usage: /* display usage */ printf("usage: t3res [options] <image-file> [operations]\n" "Options:\n" " -create - create a new resource file\n" "Operations:\n" " -add - add the following resource files (default)\n" " -recurse - recursive - include files in " "subdirectories (default)\n" " -norecurse - do not include files in subdirectories\n" " <file> - add the file\n" " <dir> - add files in the directory\n" " <file>=<res> - add file, using <res> as resource name\n" "\n" "-add is assumed if no conflicting option is specified.\n" "If no resource name is explicitly provided for a file, " "the resource is named\n" "by converting the filename to a URL-style resource name.\n"); /* give up */ exit_stat = OSEXFAIL; goto done; } /* get the image filename */ image_fname = argv[curarg]; /* create our resource list */ res_list = new CRcResList(); /* parse the operations list */ for (++curarg ; curarg < argc ; ++curarg) { /* check for an option */ if (argv[curarg][0] == '-') { /* see what we have */ if (strcmp(argv[curarg], "-add")) { /* set 'add' mode */ op_mode = RCMAIN_RES_OP_MODE_ADD; } else if (strcmp(argv[curarg], "-recurse")) { /* set recursive mode */ recurse = TRUE; } else if (strcmp(argv[curarg], "-norecurse")) { /* set non-recursive mode */ recurse = FALSE; } else { /* invalid option */ goto show_usage; } } else { char *p; char *alias; /* check for an alias */ for (p = argv[curarg] ; *p != '\0' && *p != '=' ; ++p) ; if (*p == '=') { /* * overwrite the '=' with a null byte so that the * filename ends here */ *p = '\0'; /* the alias starts after the '=' */ alias = p + 1; } else { /* there's no alias */ alias = 0; } /* it's a file - add the file to the operations list */ res_list->add_file(argv[curarg], alias, recurse); } } /* * if we're not creating, and the image doesn't exist, try adding * the default image file extension */ if (!create && osfacc(image_fname)) { strcpy(image_buf, image_fname); os_defext(image_buf, "t3"); /* formerly "t3x" */ image_fname = image_buf; } /* we've parsed the arguments - go apply the operations list */ err = CResCompMain::add_resources(image_fname, res_list, &hostifc, create, OSFTT3IMG, FALSE); /* set the appropriate exit status */ exit_stat = (err ? OSEXFAIL : OSEXSUCC); done: /* delete the resource list if we created one */ if (res_list != 0) delete res_list; /* show any unfreed memory (if we're in a debug build) */ t3_list_memory_blocks(0); /* exit with current status */ return exit_stat; }