/*- * Copyright (c) 2006 Robert N. M. Watson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * DOS compatibility routines. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dos.h" #include "emul.h" int _osmajor = 10; int _osminor = 0; /* * DOS has a notion of a set of file system roots, each with an assigned * number. Each file system has its own current working directory for the * process. We maintain a set of mappings from drive numbers to root * directories and sub-directories. */ static FILE *dos_logfile; /* Where to send errors. */ static int dos_curdisk; /* Current working disk. */ static int dos_diskcount; /* How many disks? */ /* * If dve_unixroot is a zero-length string, the drive is not present. * * dve_unixroot is a UNIX path. * * dve_wd is a DOS path. */ struct disk_vector_entry { char dve_unixpath[PATH_MAX]; /* Where in UNIX file system? */ char dve_wd[PATH_MAX]; /* Working directory on drive. */ }; static struct disk_vector_entry dos_disk_vector[26]; /* * Set up the mapping between DOS and UNIX drives and paths, set initial * working drive and directory. */ void dos_init(const char *logfile, const char *disk_vector[], const char *dir) { int i; if (logfile != NULL) { dos_logfile = fopen(logfile, "w"); if (dos_logfile == NULL) panic("dos_init: fopen('%s'): %s", logfile, strerror(errno)); } dos_curdisk = 0; if (disk_vector == NULL) return; for (i = 0; i < 26; i++) { if (disk_vector[i] == NULL) break; strlcpy(dos_disk_vector[i].dve_unixpath, disk_vector[i], sizeof(dos_disk_vector[i].dve_unixpath)); dos_diskcount++; } if (dos_chdir(dir) < 0) panic("dos_init_disk_vector: chdir('%s'): %s", dir, strerror(errno)); } static int dos_disk_valid(int disk) { if (disk < 0) return (0); if (disk >= dos_diskcount) return (0); if (strlen(dos_disk_vector[disk].dve_unixpath) == 0) return (0); return (1); } /* * Rewind one '..' on a DOS path, if possible. */ static void dos_path_dotdot(char *path) { char *cp; cp = strrchr(path, '\\'); if (cp != NULL) *cp = '\0'; } /* * One of the properties of DOS is that many filename variations can match a * particular filename. This is because spaces at the end of the filename * will be ignored, and because the '.' in filenames without extensions can * be ignored. This routine takes a segent and puts it into a canonical * form. */ static void dos_canonicalize(char *filename) { char name[PATH_MAX]; char *p1, *p2; strlcpy(name, filename, sizeof(name)); /* * Split the string into parts before and after the extension. If * there is any additional extension, it is at this point lost. */ p2 = name; p1 = strsep(&p2, "."); /* * Trim trailing white space in both parts. */ while (strlen(p1) > 0 && p1[strlen(p1) - 1] == ' ') p1[strlen(p1) - 1] = '\0'; if (p2 != NULL) { while (strlen(p2) > 0 && p2[strlen(p2) - 1] == ' ') p2[strlen(p2) - 1] = '\0'; } if (p2 == NULL) strcpy(filename, p1); else sprintf(filename, "%s.%s", p1, p2); } /* * Attempt to construct a UNIX path given a DOS path. This is a bit tricky. * If we fail, return (-1, EINVAL), otherwise (0). * * The buffer passed in must be of at least size PATH_MAX. * * DOS path names are complicated. Paths can include a drive identifier; if * not, they are with respect to the current drive. They can also be * relative or absolute, and if relative, they are relative to the current * working directory of the drive in question. */ static int dospath2unixpath(const char *dos_path, char *unix_path) { char string[PATH_MAX], upath[PATH_MAX]; int depth, drive, relative; char *cp, *sp; /* * First: determine whether there is a drive identifier in there or * not. If so, validate. If not, use the current disk drive. */ if (strlen(dos_path) >= 2 && dos_path[1] == ':') { /* * Drive letters must be between 'a' and 'z'. * * XXXRW: validate drive? */ drive = tolower(dos_path[0]); if (drive < 'a' || drive > 'z') { errno = ENXIO; return (-1); } drive -= 'a'; dos_path += 2; } else drive = dos_curdisk; /* * There must be at most one ':' in the path, so if there are any at * this point, we have a problem. Disallow '/' as well. */ if (strchr(dos_path, ':') != NULL || strchr(dos_path, '/') != NULL) { errno = EINVAL; return (-1); } /* * Determine if an absolute or relative path. */ if (dos_path[0] == '\\') { relative = 0; dos_path++; } else relative = 1; /* * First, copy the UNIX root for the drive into the writable buffer. */ snprintf(unix_path, PATH_MAX, "%s/", dos_disk_vector[drive].dve_unixpath); // printf("drive path: %s\n", unix_path); /* * Now, we start iterating down the path -- first the relative, if * present, and then into the user path. We do it segment by * segment, lower-casing as we go, and checking for excess '..'s. * Processing '..'s is a pain; we track current depth using 'depth'. * * XXXRW: We don't handle buffer overflows here properly. */ depth = 0; strcpy(upath, ""); if (relative) { strlcpy(string, dos_disk_vector[drive].dve_wd, PATH_MAX); sp = string; while ((cp = strsep(&sp, "\\")) != NULL) { if (strcmp(cp, "..") == 0) dos_path_dotdot(upath); else { strcat(upath, cp); strcat(upath, "/"); } } } // printf("path after relative: '%s' + '%s'\n", upath, string); /* * Now the user bit of the path. A tricky part of this is that all * DOS files have extensions, even if they are empty, and may also * have spaces. When copying, we remove the trailing spaces before * the 'dot', and if there are no non-space characters after the dot, * we also remove the dot. * * XXXRW: Buffer overflows. */ strcpy(string, dos_path); sp = string; while ((cp = strsep(&sp, "\\")) != NULL) { if (strcmp(cp, "..") == 0) dos_path_dotdot(upath); else { dos_canonicalize(cp); strcat(upath, cp); if (sp != NULL) strcat(upath, "/"); } } // printf("after user path: '%s'\n", upath); /* * Finally, reduce some DOS-isms. */ for (cp = upath; *cp != '\0'; cp++) { if (*cp == '\\') *cp = '/'; *cp = tolower(*cp); } strcat(unix_path, upath); // printf("d2u: %s -> %s\n", dos_path, unix_path); return (0); } int filelength(int fd) { struct stat sb; int error; error = fstat(fd, &sb); if (error) panic("filelength: fstat: %s", strerror(errno)); return (sb.st_size); } int _chmod(const char *filename, int pmode, int fauxarg) { char path[PATH_MAX]; mode_t mode; mode = 0; if ((pmode | _S_IWRITE | _S_IREAD) != pmode && pmode != 1) panic("_chmod: invalid mode %o", pmode); if ((pmode & _S_IWRITE) || (mode == 1)) mode |= 0400; if (pmode & _S_IREAD) mode |= 0200; if (dospath2unixpath(filename, path) == -1) return (-1); return (chmod(path, mode)); } void getdate(struct date *date) { struct timeval tv; struct tm tm; time_t t; if (gettimeofday(&tv, NULL) < 0) panic("getdate: gettimeofday: %s", strerror(errno)); t = tv.tv_sec; localtime_r(&t, &tm); date->da_year = tm.tm_year + 1900; date->da_day = tm.tm_mday; date->da_mon = tm.tm_mon; } void gettime(struct time *time) { struct timeval tv; struct tm tm; time_t t; if (gettimeofday(&tv, NULL) < 0) panic("gettime: gettimeofday: %s", strerror(errno)); t = tv.tv_sec; localtime_r(&t, &tm); time->ti_hour = tm.tm_hour; time->ti_min = tm.tm_min; time->ti_sec = tm.tm_sec; time->ti_hund = tv.tv_usec / 10000; } int getftime(int fd, struct ftime *ftime) { panic("getftime: %s", strerror(ENOSYS)); } int setftime(int fd, struct ftime *ftime) { panic("setftime: %s", strerror(ENOSYS)); } /* * fnsplit() appears to be quite poorly specified, so we take a hack at it * and reflect on its inadequacy. */ int fnsplit(const char *path, char *drive, char *dir, char *name, char *ext) { char *bufp, *cp, *dirp, *extp, *filep; char buffer[MAXPATH]; int flags; flags = 0; if (drive != NULL) drive[0] = '\0'; if (dir != NULL) dir[0] = '\0'; if (name != NULL) name[0] = '\0'; if (ext != NULL) ext[0] = '\0'; /* * Make a local writable copy. */ strlcpy(buffer, path, MAXPATH); bufp = buffer; /* * Drive letter? */ if (strlen(bufp) > 2 && bufp[1] == ':') { if (drive != NULL) { sprintf(drive, "%c:", toupper(bufp[1])); flags |= DRIVE; } bufp += 2; } /* * Now see if we can find a trailing filename by looking for the last * bit of the path. */ cp = strrchr(bufp, '\\'); if (cp == NULL) { /* * Just a filename. */ filep = bufp; dirp = NULL; } else { /* * A path and a filename. */ *cp = '\0'; cp++; filep = cp; dirp = bufp; } if (dirp != NULL) { flags |= DIRECTORY; strcpy(dir, dirp); } /* * See if we can identify a file extension. There are two special * cases for filenames with dots in them: "." and "..". Also handle * the extension-free case in the same way. */ if ((strcmp(filep, ".") == 0) || (strcmp(filep, "..") == 0) || (strchr(filep, '.') == NULL)) { flags |= FILENAME; strcpy(name, filep); } else { extp = filep; strsep(&extp, "."); flags |= EXTENSION; flags |= FILENAME; strcpy(name, filep); strcpy(ext, "."); strcat(ext, extp); } #if 0 printf("fnsplit(%s) to ", path); if (flags & DRIVE) printf("drive %s ", drive); if (flags & DIRECTORY) printf("directory %s ", dir); if (flags & FILENAME) printf("filename %s ", name); if (flags & EXTENSION) printf("extension %s ", ext); printf("flags 0x%x\n", flags); #endif return (flags); } /* * Change the DOS notion of current working directory, and also possibly * current drive. * * XXXRW: What happens if one operation is valid and the other isn't? */ int dos_chdir(const char *filename) { char upath[PATH_MAX]; struct stat sb; int drive; // printf("dos_chdir('%s')\n", filename); /* * First, convert to a UNIX directory, and make sure it exists/etc. */ if (dospath2unixpath(filename, upath) < 0) return (-1); if (stat(upath, &sb) < 0) return (-1); if (!(S_ISDIR(sb.st_mode))) { errno = ENOTDIR; return (-1); } /* * Next, re-parse and set up the working drive and directory. */ if (strlen(filename) >= 2 && filename[1] == ':') { drive = tolower(filename[0]) - 'a'; if (!(dos_disk_valid(drive))) { errno = ENXIO; return (-1); } filename += 2; } dos_curdisk = drive; /* * Remove leading and trailing '\\'s from the path so things are * cleaner later. */ while (filename[0] == '\\') filename++; strlcpy(dos_disk_vector[drive].dve_wd, filename, sizeof(dos_disk_vector[drive].dve_wd)); while (dos_disk_vector[drive].dve_wd[ strlen(dos_disk_vector[drive].dve_wd) - 1] == '\\') dos_disk_vector[drive].dve_wd[ strlen(dos_disk_vector[drive].dve_wd) - 1] = '\0'; return (0); } int dos_mkdir(const char *filename) { char upath[PATH_MAX]; if (dospath2unixpath(filename, upath) < 0) return (-1); return (mkdir(upath, S_IRWXU)); } int dos_open(const char *path, int flags, ...) { char upath[PATH_MAX]; int fd, mode; va_list ap; va_start(ap, flags); if (flags & O_CREAT) mode = va_arg(ap, int); else mode = 0; va_end(ap); if (dospath2unixpath(path, upath) < 0) return (-1); /* XXXRW: Be conservative on locking. */ flags &= 0x00ffffff; flags |= O_EXLOCK; fd = open(upath, flags, mode); if (fd < 0 && dos_logfile != NULL) { fprintf(dos_logfile, "dos_open: open(%s, %x, %o): %s\n", upath, flags, mode, strerror(errno)); fflush(dos_logfile); } return (fd); } int findfirst(const char *pathname, struct ffblk *ffblk, int attrib) { char path[PATH_MAX]; char *cp; glob_t g; if (dospath2unixpath(pathname, path) < 0) return (-1); bzero(ffblk, sizeof(*ffblk)); strlcpy(ffblk->_ff_pattern, path, PATH_MAX); if (glob(path, 0, NULL, &g) < 0) return (-1); if (g.gl_matchc == 0) return (-1); ffblk->_ff_next = 1; strlcpy(ffblk->ff_name, g.gl_pathv[0], PATH_MAX); for (cp = ffblk->ff_name; *cp != '\0'; cp++) *cp = toupper(*cp); globfree(&g); return (0); } int findnext(struct ffblk *ffblk) { char *cp; glob_t g; if (glob(ffblk->_ff_pattern, 0, NULL, &g) < 0) return (-1); if (g.gl_matchc == 0) return (-1); if (g.gl_matchc <= ffblk->_ff_next) return (-1); strlcpy(ffblk->ff_name, g.gl_pathv[ffblk->_ff_next], PATH_MAX); for (cp = ffblk->ff_name; *cp != '\0'; cp++) *cp = toupper(*cp); ffblk->_ff_next++; globfree(&g); return (0); } #ifndef timespecadd #define timespecadd(vvp, uvp) \ do { \ (vvp)->tv_sec += (uvp)->tv_sec; \ (vvp)->tv_nsec += (uvp)->tv_nsec; \ if ((vvp)->tv_nsec >= 1000000000) { \ (vvp)->tv_sec++; \ (vvp)->tv_nsec -= 1000000000; \ } \ } while (0) #endif void delay(int millisec) { struct timespec total, left; total.tv_sec = millisec / 1000; total.tv_nsec = (millisec % 1000) * 1000000; left.tv_sec = 0; left.tv_nsec = 0; while (nanosleep(&total, &left) != 0) { if (errno != EINTR) panic("nanosleep: %s", strerror(errno)); total = left; } } void unixtodos(time_t time, struct date *d, struct time *t) { panic("unixtodos: %s", strerror(ENOSYS)); } time_t dostounix(struct date *d, struct dostime *t) { panic("dostounix: %s", strerror(ENOSYS)); } /* * XXXRW: For now, equate blocks, sectors, and clusters. * * XXXRW: Make no attempt to handle wraparound. This is a bug. * * Return a zero'd buffer if the drive isn't present. */ void getdfree(unsigned char drive, struct dfree *ptr) { struct statfs sf; bzero(ptr, sizeof(*ptr)); if (!(dos_disk_valid(drive))) return; if (statfs(dos_disk_vector[drive].dve_unixpath, &sf) < 0) panic("statfs: %s", strerror(errno)); ptr->df_avail = sf.f_bavail; ptr->df_total = sf.f_blocks; ptr->df_bsec = sf.f_bsize; ptr->df_sclus = 1; } int getcurdir(int drive, char *direc) { if (!(dos_disk_valid(drive))) { errno = ENXIO; return (-1); } strcpy(direc, dos_disk_vector[drive].dve_wd); return (0); } /* * There appears to be no defined failure mode for this function. We don't * allow dos_curdisk to be >= dos_diskcount, so we won't change the drive if * an invalid one is requested. Also, if no disks are defined, return 0. */ int setdisk(int drive) { if (dos_disk_valid(drive)) dos_curdisk = drive; return (dos_diskcount); } int chsize(int fildes, long size) { return (ftruncate(fildes, size)); } int getdisk(void) { return (dos_curdisk); } /* * Search current directory and path for a file. Return the full file path * in a static buffer, or NULL. */ char * searchpath(const char *file) { char path[PATH_MAX], checkname[PATH_MAX], *pathp, *curdir; static char matchpath[PATH_MAX]; if (access(file, F_OK) == 0) { strlcpy(matchpath, file, sizeof(matchpath)); return (matchpath); } pathp = getenv("PATH"); if (path == NULL) return (NULL); strlcpy(path, pathp, sizeof(path)); pathp = path; while ((curdir = strsep(&pathp, ":")) != NULL) { if (snprintf(checkname, sizeof(checkname), "%s/%s", curdir, file) >= sizeof(checkname)) continue; if (access(checkname, F_OK) == 0) { strlcpy(matchpath, checkname, sizeof(matchpath)); return (matchpath); } } return (NULL); }