1997-08-09 01:43:15 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 1992, 1993, 1996
|
|
|
|
* Berkeley Software Design, Inc. 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.
|
|
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
|
|
* must display the following acknowledgement:
|
|
|
|
* This product includes software developed by Berkeley Software
|
|
|
|
* Design, Inc.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``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 Berkeley Software Design, Inc. 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.
|
|
|
|
*
|
|
|
|
* BSDI cwd.c,v 2.2 1996/04/08 19:32:25 bostic Exp
|
|
|
|
*
|
1999-08-28 01:08:13 +00:00
|
|
|
* $FreeBSD$
|
1997-08-09 01:43:15 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdio.h>
|
2001-07-24 11:50:23 +00:00
|
|
|
|
1997-08-09 01:43:15 +00:00
|
|
|
#include "doscmd.h"
|
2001-07-24 11:50:23 +00:00
|
|
|
#include "cwd.h"
|
1997-08-09 01:43:15 +00:00
|
|
|
|
2001-08-03 10:47:56 +00:00
|
|
|
/* Local functions */
|
|
|
|
static inline int isvalid(unsigned);
|
|
|
|
static inline int isdot(unsigned);
|
|
|
|
static inline int isslash(unsigned);
|
|
|
|
static void to_dos_fcb(u_char *, u_char *);
|
|
|
|
|
1997-08-09 01:43:15 +00:00
|
|
|
#define D_REDIR 0x0080000 /* XXX - ack */
|
|
|
|
#define D_TRAPS3 0x0200000
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
u_char *path;
|
|
|
|
u_char *cwd;
|
|
|
|
int len;
|
|
|
|
int maxlen;
|
|
|
|
int read_only:1;
|
|
|
|
} Path_t;
|
|
|
|
|
|
|
|
typedef struct Name_t {
|
|
|
|
u_char *real;
|
|
|
|
struct Name_t *next;
|
|
|
|
u_char name[9];
|
|
|
|
u_char ext[4];
|
|
|
|
} Name_t;
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_DRIVE 26
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
static Path_t paths[MAX_DRIVE];
|
1997-08-09 01:43:15 +00:00
|
|
|
static Name_t *names;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the drive to be based at 'base' in the BSD filesystem
|
|
|
|
*/
|
|
|
|
void
|
2001-12-02 13:48:40 +00:00
|
|
|
init_path(int drive, const u_char *base, const u_char *dir)
|
1997-08-09 01:43:15 +00:00
|
|
|
{
|
|
|
|
Path_t *d;
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
debug(D_TRAPS3, "init_path(%d, %s, %s)\n", drive, base, dir);
|
|
|
|
|
|
|
|
d = &paths[drive];
|
|
|
|
|
|
|
|
if (d->path)
|
|
|
|
free(d->path);
|
|
|
|
|
|
|
|
if ((d->path = ustrdup(base)) == NULL)
|
1998-07-01 19:56:17 +00:00
|
|
|
fatal("strdup in init_path for %c:%s: %s", drntol(drive), base,
|
1997-08-09 01:43:15 +00:00
|
|
|
strerror(errno));
|
|
|
|
|
|
|
|
if (d->maxlen < 2) {
|
|
|
|
d->maxlen = 128;
|
|
|
|
if ((d->cwd = (u_char *)malloc(d->maxlen)) == NULL)
|
1998-07-01 19:56:17 +00:00
|
|
|
fatal("malloc in init_path for %c:%s: %s", drntol(drive), base,
|
1997-08-09 01:43:15 +00:00
|
|
|
strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
d->cwd[0] = '\\';
|
|
|
|
d->cwd[1] = 0;
|
|
|
|
d->len = 1;
|
|
|
|
if (dir) {
|
|
|
|
if (ustrncmp(base, dir, ustrlen(base)) == 0)
|
|
|
|
dir += ustrlen(base);
|
|
|
|
while (*dir == '/')
|
|
|
|
++dir;
|
|
|
|
|
|
|
|
while (*dir) {
|
|
|
|
u_char dosname[15];
|
|
|
|
u_char realname[256];
|
|
|
|
u_char *r = realname;;
|
|
|
|
|
|
|
|
while ((*r = *dir) && *dir++ != '/') {
|
|
|
|
++r;
|
|
|
|
}
|
|
|
|
*r = 0;
|
|
|
|
while (*dir == '/')
|
|
|
|
++dir;
|
|
|
|
|
1998-07-01 19:56:17 +00:00
|
|
|
dosname[0] = drntol(drive);
|
1997-08-09 01:43:15 +00:00
|
|
|
dosname[1] = ':';
|
|
|
|
real_to_dos(realname, &dosname[2]);
|
|
|
|
|
|
|
|
if (dos_setcwd(dosname)) {
|
|
|
|
fprintf(stderr, "Failed to CD to directory %s in %s\n",
|
|
|
|
dosname, d->cwd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mark this drive as read only
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
dos_makereadonly(int drive)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return;
|
|
|
|
paths[drive].read_only = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return read-only status of drive
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
dos_readonly(int drive)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return (0);
|
|
|
|
debug(D_REDIR, "dos_readonly(%d) -> %d\n", drive, paths[drive].read_only);
|
|
|
|
return (paths[drive].read_only);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return DOS's idea of the CWD for drive
|
|
|
|
* Return 0 if the drive specified is not mapped (or bad)
|
|
|
|
*/
|
|
|
|
u_char *
|
|
|
|
dos_getcwd(int drive)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return (0);
|
|
|
|
debug(D_REDIR, "dos_getcwd(%d) -> %s\n", drive, paths[drive].cwd);
|
|
|
|
return (paths[drive].cwd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return DOS's idea of the CWD for drive
|
|
|
|
* Return 0 if the drive specified is not mapped (or bad)
|
|
|
|
*/
|
|
|
|
u_char *
|
|
|
|
dos_getpath(int drive)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return (0);
|
|
|
|
debug(D_REDIR, "dos_getpath(%d) -> %s\n", drive, paths[drive].path);
|
|
|
|
return (paths[drive].path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fix up a DOS path name. Strip out all '.' and '..' entries, turn
|
|
|
|
* '/' into '\\' and convert all lowercase to uppercase.
|
|
|
|
* Returns 0 on success or DOS errno
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
dos_makepath(u_char *where, u_char *newpath)
|
|
|
|
{
|
|
|
|
int drive;
|
|
|
|
u_char **dirs;
|
|
|
|
u_char *np;
|
|
|
|
Path_t *d;
|
|
|
|
u_char tmppath[1024];
|
1999-11-08 17:28:46 +00:00
|
|
|
u_char *snewpath = newpath;
|
1997-08-09 01:43:15 +00:00
|
|
|
|
|
|
|
if (where[0] != '\0' && where[1] == ':') {
|
1998-07-01 19:56:17 +00:00
|
|
|
drive = drlton(*where);
|
1997-08-09 01:43:15 +00:00
|
|
|
*newpath++ = *where++;
|
|
|
|
*newpath++ = *where++;
|
|
|
|
} else {
|
|
|
|
drive = diskdrive;
|
1998-07-01 19:56:17 +00:00
|
|
|
*newpath++ = drntol(diskdrive);
|
1997-08-09 01:43:15 +00:00
|
|
|
*newpath++ = ':';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE) {
|
1998-07-01 19:56:17 +00:00
|
|
|
debug(D_REDIR,"drive %c invalid\n", drntol(drive));
|
1997-08-09 01:43:15 +00:00
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
}
|
|
|
|
|
|
|
|
d = &paths[drive];
|
|
|
|
if (d->cwd == NULL) {
|
1998-07-01 19:56:17 +00:00
|
|
|
debug(D_REDIR,"no cwd for drive %c\n",drntol(drive));
|
1997-08-09 01:43:15 +00:00
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(D_REDIR, "dos_makepath(%d, %s)\n", drive, where);
|
|
|
|
|
|
|
|
np = newpath;
|
|
|
|
if (*where != '\\' && *where != '/') {
|
2001-06-28 12:02:45 +00:00
|
|
|
ustrncpy(tmppath, d->cwd, 1024);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (d->cwd[1])
|
2001-12-02 13:48:40 +00:00
|
|
|
ustrncat(tmppath, "/", 1024 - ustrlen(tmppath));
|
2001-06-28 12:02:45 +00:00
|
|
|
ustrncat(tmppath, where, 1024 - ustrlen(tmppath));
|
1997-08-09 01:43:15 +00:00
|
|
|
} else {
|
2001-06-28 12:02:45 +00:00
|
|
|
ustrncpy(tmppath, where, 1024 - ustrlen(tmppath));
|
1997-08-09 01:43:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dirs = get_entries(tmppath);
|
|
|
|
if (dirs == NULL)
|
|
|
|
return (PATH_NOT_FOUND);
|
|
|
|
|
|
|
|
np = newpath;
|
|
|
|
while (*dirs) {
|
|
|
|
u_char *dir = *dirs++;
|
|
|
|
if (*dir == '/' || *dir == '\\') {
|
|
|
|
np = newpath + 1;
|
|
|
|
newpath[0] = '\\';
|
|
|
|
} else if (dir[0] == '.' && dir[1] == 0) {
|
|
|
|
;
|
|
|
|
} else if (dir[0] == '.' && dir[1] == '.' && dir[2] == '\0') {
|
|
|
|
while (np[-1] != '/' && np[-1] != '\\')
|
|
|
|
--np;
|
|
|
|
if (np - 1 > newpath)
|
|
|
|
--np;
|
|
|
|
} else {
|
|
|
|
if (np[-1] != '\\')
|
|
|
|
*np++ = '\\';
|
1999-11-08 17:28:46 +00:00
|
|
|
while ((*np = *dir++) && np - snewpath < 1023)
|
1997-08-09 01:43:15 +00:00
|
|
|
++np;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*np = 0;
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set DOS's idea of the CWD for drive to be where.
|
|
|
|
* Returns DOS errno on failuer.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
dos_setcwd(u_char *where)
|
|
|
|
{
|
2001-07-24 11:50:23 +00:00
|
|
|
u_char new_path[1024];
|
|
|
|
u_char real_path[1024];
|
1997-08-09 01:43:15 +00:00
|
|
|
int drive;
|
|
|
|
struct stat sb;
|
|
|
|
Path_t *d;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
debug(D_REDIR, "dos_setcwd(%s)\n", where);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
error = dos_makepath(where, new_path);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (error)
|
|
|
|
return (error);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
error = dos_to_real_path(new_path, real_path, &drive);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (error)
|
|
|
|
return (error);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
if (ustat(real_path, &sb) < 0 || !S_ISDIR(sb.st_mode))
|
1997-08-09 01:43:15 +00:00
|
|
|
return (PATH_NOT_FOUND);
|
2001-07-24 11:50:23 +00:00
|
|
|
if (uaccess(real_path, R_OK | X_OK))
|
1997-08-09 01:43:15 +00:00
|
|
|
return (PATH_NOT_FOUND);
|
|
|
|
|
|
|
|
d = &paths[drive];
|
2001-07-24 11:50:23 +00:00
|
|
|
d->len = ustrlen(new_path + 2);
|
1997-08-09 01:43:15 +00:00
|
|
|
|
|
|
|
if (d->len + 1 > d->maxlen) {
|
|
|
|
free(d->cwd);
|
|
|
|
d->maxlen = d->len + 1 + 32;
|
|
|
|
d->cwd = (u_char *)malloc(d->maxlen);
|
|
|
|
if (d->cwd == NULL)
|
1998-07-01 19:56:17 +00:00
|
|
|
fatal("malloc in dos_setcwd for %c:%s: %s", drntol(drive),
|
2001-07-24 11:50:23 +00:00
|
|
|
new_path, strerror(errno));
|
1997-08-09 01:43:15 +00:00
|
|
|
}
|
2001-07-24 11:50:23 +00:00
|
|
|
ustrncpy(d->cwd, new_path + 2, d->maxlen - d->len);
|
1997-08-09 01:43:15 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2001-07-24 11:50:23 +00:00
|
|
|
* Given a DOS path dos_path and a drive, convert it to a BSD pathname
|
|
|
|
* and store the result in real_path.
|
1997-08-09 01:43:15 +00:00
|
|
|
* Return DOS errno on failure.
|
|
|
|
*/
|
|
|
|
int
|
2001-07-24 11:50:23 +00:00
|
|
|
dos_to_real_path(u_char *dos_path, u_char *real_path, int *drivep)
|
1997-08-09 01:43:15 +00:00
|
|
|
{
|
|
|
|
Path_t *d;
|
2001-07-24 11:50:23 +00:00
|
|
|
u_char new_path[1024];
|
1997-08-09 01:43:15 +00:00
|
|
|
u_char *rp;
|
|
|
|
u_char **dirs;
|
|
|
|
u_char *dir;
|
|
|
|
int drive;
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
debug(D_REDIR, "dos_to_real_path(%s)\n", dos_path);
|
1997-08-09 01:43:15 +00:00
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
if (dos_path[0] != '\0' && dos_path[1] == ':') {
|
|
|
|
drive = drlton(*dos_path);
|
|
|
|
dos_path++;
|
|
|
|
dos_path++;
|
1997-08-09 01:43:15 +00:00
|
|
|
} else {
|
|
|
|
drive = diskdrive;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = &paths[drive];
|
|
|
|
if (d->cwd == NULL)
|
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
ustrcpy(real_path, d->path);
|
1997-08-09 01:43:15 +00:00
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
rp = real_path;
|
1997-08-09 01:43:15 +00:00
|
|
|
while (*rp)
|
|
|
|
++rp;
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
ustrncpy(new_path, dos_path, 1024 - ustrlen(new_path));
|
1997-08-09 01:43:15 +00:00
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
dirs = get_entries(new_path);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (dirs == NULL)
|
|
|
|
return (PATH_NOT_FOUND);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip the leading /
|
|
|
|
* There are no . or .. entries to worry about either
|
|
|
|
*/
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
while ((dir = *++dirs) != 0) {
|
1997-08-09 01:43:15 +00:00
|
|
|
*rp++ = '/';
|
|
|
|
dos_to_real(dir, rp);
|
|
|
|
while (*rp)
|
|
|
|
++rp;
|
|
|
|
}
|
|
|
|
|
|
|
|
*drivep = drive;
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Provide a few istype() style functions.
|
|
|
|
* isvalid: True if the character is a valid DOS filename character
|
|
|
|
* isdot: True if '.'
|
|
|
|
* isslash: True if '/' or '\'
|
|
|
|
*
|
|
|
|
* 0 - invalid
|
|
|
|
* 1 - okay
|
|
|
|
* 2 - *
|
|
|
|
* 3 - dot
|
|
|
|
* 4 - slash
|
|
|
|
* 5 - colon
|
|
|
|
* 6 - ?
|
|
|
|
* 7 - lowercase
|
|
|
|
*/
|
|
|
|
u_char cattr[256] = {
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 */
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */
|
|
|
|
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 1, 3, 4, /* 0x20 */
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 0, 0, 0, 0, 6, /* 0x30 */
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 */
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 1, 1, /* 0x50 */
|
|
|
|
1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* 0x60 */
|
|
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 0, 1, 1, 0, /* 0x70 */
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 */
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
};
|
|
|
|
|
2001-08-03 10:47:56 +00:00
|
|
|
static inline int
|
1997-08-09 01:43:15 +00:00
|
|
|
isvalid(unsigned c)
|
|
|
|
{
|
|
|
|
return (cattr[c & 0xff] == 1);
|
|
|
|
}
|
|
|
|
|
2001-08-03 10:47:56 +00:00
|
|
|
static inline int
|
1997-08-09 01:43:15 +00:00
|
|
|
isdot(unsigned c)
|
|
|
|
{
|
|
|
|
return (cattr[c & 0xff] == 3);
|
|
|
|
}
|
|
|
|
|
2001-08-03 10:47:56 +00:00
|
|
|
static inline int
|
1997-08-09 01:43:15 +00:00
|
|
|
isslash(unsigned c)
|
|
|
|
{
|
|
|
|
return (cattr[c & 0xff] == 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given a real component, compute the DOS component.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
real_to_dos(u_char *real, u_char *dos)
|
|
|
|
{
|
|
|
|
Name_t *n;
|
|
|
|
Name_t *nn;
|
|
|
|
u_char *p;
|
|
|
|
u_char nm[9], ex[4];
|
|
|
|
int ncnt, ecnt;
|
|
|
|
int echar = '0';
|
|
|
|
int nchar = '0';
|
|
|
|
|
|
|
|
if (real[0] == '.' && (real[1] == '\0'
|
|
|
|
|| (real[1] == '.' && real[2] == '\0'))) {
|
|
|
|
sprintf((char *)dos, "%.8s", real);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
n = names;
|
|
|
|
while (n) {
|
|
|
|
if (ustrcmp(real, n->real) == 0) {
|
|
|
|
if (n->ext[0])
|
|
|
|
sprintf((char *)dos, "%.8s.%.3s", n->name, n->ext);
|
|
|
|
else
|
|
|
|
sprintf((char *)dos, "%.8s", n->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
n = n->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = real;
|
|
|
|
ncnt = ecnt = 0;
|
|
|
|
while (isvalid(*p) && ncnt < 8) {
|
|
|
|
nm[ncnt] = *p;
|
|
|
|
++ncnt;
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
if (isdot(*p)) {
|
|
|
|
++p;
|
|
|
|
while (isvalid(*p) && ecnt < 3) {
|
|
|
|
ex[ecnt] = *p;
|
|
|
|
++ecnt;
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nm[ncnt] = '\0';
|
|
|
|
ex[ecnt] = '\0';
|
|
|
|
|
|
|
|
if (!*p && ncnt <= 8 && ecnt <= 3) {
|
|
|
|
n = names;
|
|
|
|
while (n) {
|
|
|
|
if (ustrncmp(n->name, nm, 8) == 0 && ustrncmp(n->ext, ex, 3) == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
n = n->next;
|
|
|
|
}
|
|
|
|
if (n == 0) {
|
|
|
|
ustrcpy(dos, real);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n = (Name_t *)malloc(sizeof(Name_t));
|
|
|
|
|
|
|
|
if (!n)
|
|
|
|
fatal("malloc in real_to_dos: %s\n", strerror(errno));
|
|
|
|
|
|
|
|
n->real = ustrdup(real);
|
|
|
|
|
|
|
|
if (!n->real)
|
|
|
|
fatal("strdup in real_to_dos: %s\n", strerror(errno));
|
|
|
|
|
|
|
|
p = real;
|
|
|
|
ncnt = ecnt = 0;
|
|
|
|
while (*p && ncnt < 8) {
|
|
|
|
if (isvalid(*p))
|
|
|
|
n->name[ncnt] = *p;
|
|
|
|
else if (islower(*p))
|
|
|
|
n->name[ncnt] = toupper(*p);
|
|
|
|
else if (isdot(*p))
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
n->name[ncnt] = (*p |= 0x80);
|
|
|
|
++ncnt;
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
if (isdot(*p)) {
|
|
|
|
++p;
|
|
|
|
while (*p && ecnt < 3) {
|
|
|
|
if (isvalid(*p))
|
|
|
|
n->ext[ecnt] = *p;
|
|
|
|
else if (islower(*p))
|
|
|
|
n->ext[ecnt] = toupper(*p);
|
|
|
|
#if 0
|
|
|
|
else if (isdot(*p))
|
|
|
|
ERROR
|
|
|
|
#endif
|
|
|
|
else
|
|
|
|
n->ext[ecnt] = (*p |= 0x80);
|
|
|
|
++ecnt;
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n->name[ncnt] = '\0';
|
|
|
|
n->ext[ecnt] = '\0';
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
nn = names;
|
|
|
|
while (nn) {
|
|
|
|
if (ustrncmp(n->name, nn->name, 8) == 0 &&
|
|
|
|
ustrncmp(n->ext, nn->ext, 3) == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nn = nn->next;
|
|
|
|
}
|
|
|
|
if (!nn)
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* Dang, this name was already in the cache.
|
|
|
|
* Let's munge it a little and try again.
|
|
|
|
*/
|
|
|
|
if (ecnt < 3) {
|
|
|
|
n->ext[ecnt] = echar;
|
|
|
|
if (echar == '9') {
|
|
|
|
echar = 'A';
|
|
|
|
} else if (echar == 'Z') {
|
|
|
|
++ecnt;
|
|
|
|
echar = '0';
|
|
|
|
} else {
|
|
|
|
++echar;
|
|
|
|
}
|
|
|
|
} else if (ncnt < 8) {
|
|
|
|
n->name[ncnt] = nchar;
|
|
|
|
if (nchar == '9') {
|
|
|
|
nchar = 'A';
|
|
|
|
} else if (nchar == 'Z') {
|
|
|
|
++ncnt;
|
|
|
|
nchar = '0';
|
|
|
|
} else {
|
|
|
|
++nchar;
|
|
|
|
}
|
|
|
|
} else if (n->ext[2] < 'Z')
|
|
|
|
n->ext[2]++;
|
|
|
|
else if (n->ext[1] < 'Z')
|
|
|
|
n->ext[1]++;
|
|
|
|
else if (n->ext[0] < 'Z')
|
|
|
|
n->ext[0]++;
|
|
|
|
else if (n->name[7] < 'Z')
|
|
|
|
n->name[7]++;
|
|
|
|
else if (n->name[6] < 'Z')
|
|
|
|
n->name[6]++;
|
|
|
|
else if (n->name[5] < 'Z')
|
|
|
|
n->name[5]++;
|
|
|
|
else if (n->name[4] < 'Z')
|
|
|
|
n->name[4]++;
|
|
|
|
else if (n->name[3] < 'Z')
|
|
|
|
n->name[3]++;
|
|
|
|
else if (n->name[2] < 'Z')
|
|
|
|
n->name[2]++;
|
|
|
|
else if (n->name[1] < 'Z')
|
|
|
|
n->name[1]++;
|
|
|
|
else if (n->name[0] < 'Z')
|
|
|
|
n->name[0]++;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n->ext[0])
|
|
|
|
sprintf((char *)dos, "%.8s.%.3s", n->name, n->ext);
|
|
|
|
else
|
|
|
|
sprintf((char *)dos, "%.8s", n->name);
|
|
|
|
n->next = names;
|
|
|
|
names = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given a DOS component, compute the REAL component.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
dos_to_real(u_char *dos, u_char *real)
|
|
|
|
{
|
|
|
|
int ncnt = 0;
|
|
|
|
int ecnt = 0;
|
|
|
|
u_char name[8];
|
|
|
|
u_char ext[3];
|
|
|
|
Name_t *n = names;
|
|
|
|
|
|
|
|
while (ncnt < 8 && (isvalid(*dos) || islower(*dos))) {
|
|
|
|
name[ncnt++] = islower(*dos) ? toupper(*dos) : *dos;
|
|
|
|
++dos;
|
|
|
|
}
|
|
|
|
if (ncnt < 8)
|
|
|
|
name[ncnt] = 0;
|
|
|
|
|
|
|
|
if (isdot(*dos)) {
|
|
|
|
while (ecnt < 3 && (isvalid(*++dos) || islower(*dos))) {
|
|
|
|
ext[ecnt++] = islower(*dos) ? toupper(*dos) : *dos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ecnt < 3)
|
|
|
|
ext[ecnt] = 0;
|
|
|
|
|
|
|
|
while (n) {
|
|
|
|
if (!ustrncmp(name, n->name, 8) && !ustrncmp(ext, n->ext, 3)) {
|
|
|
|
ustrcpy(real, n->real);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
n = n->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ext[0])
|
|
|
|
sprintf((char *)real, "%-.8s.%-.3s", name, ext);
|
|
|
|
else
|
|
|
|
sprintf((char *)real, "%-.8s", name);
|
|
|
|
|
|
|
|
while (*real) {
|
|
|
|
if (isupper(*real))
|
|
|
|
*real = tolower(*real);
|
|
|
|
++real;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* convert a path into an argv[] like vector of components.
|
|
|
|
* If the path starts with a '/' or '\' then the first entry
|
|
|
|
* will be "/" or "\". This is the only case in which a "/"
|
|
|
|
* or "\" may appear in an entry.
|
|
|
|
* Also convert all lowercase to uppercase.
|
|
|
|
* The data returned is in a static area, so a second call will
|
|
|
|
* erase the data of the first.
|
|
|
|
*/
|
|
|
|
u_char **
|
|
|
|
get_entries(u_char *path)
|
|
|
|
{
|
|
|
|
static u_char *entries[128]; /* Maximum depth... */
|
|
|
|
static u_char mypath[1024];
|
|
|
|
u_char **e = entries;
|
|
|
|
u_char *p = mypath;
|
|
|
|
|
|
|
|
ustrncpy(mypath+1, path, 1022);
|
|
|
|
p = mypath+1;
|
|
|
|
mypath[1023] = 0;
|
|
|
|
if (path[0] == '/' || path[0] == '\\') {
|
|
|
|
mypath[0] = path[0];
|
|
|
|
*e++ = mypath;
|
|
|
|
*p++ = 0;
|
|
|
|
}
|
|
|
|
while (*p && e < entries + 127) {
|
|
|
|
while (*p && (*p == '/' || *p == '\\')) {
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!*p)
|
|
|
|
break;
|
|
|
|
*e++ = p;
|
|
|
|
while (*p && (*p != '/' && *p != '\\')) {
|
|
|
|
if (islower(*p))
|
|
|
|
*p = tolower(*p);
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* skip over the '/' or '\'
|
|
|
|
*/
|
|
|
|
if (*p)
|
|
|
|
*p++ = 0;
|
|
|
|
}
|
|
|
|
*e = 0;
|
|
|
|
return (entries);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return file system statistics for drive.
|
|
|
|
* Return the DOS errno on failure.
|
|
|
|
*/
|
2001-07-24 11:50:23 +00:00
|
|
|
int
|
1997-08-09 01:43:15 +00:00
|
|
|
get_space(int drive, fsstat_t *fs)
|
|
|
|
{
|
|
|
|
Path_t *d;
|
|
|
|
struct statfs *buf;
|
|
|
|
int nfs;
|
|
|
|
int i;
|
|
|
|
struct statfs *me = 0;
|
|
|
|
|
|
|
|
if (drive < 0 || drive >= MAX_DRIVE)
|
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
|
|
|
|
d = &paths[drive];
|
|
|
|
|
|
|
|
if (!d->path)
|
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
|
|
|
|
nfs = getfsstat(0, 0, MNT_WAIT);
|
|
|
|
|
|
|
|
buf = (struct statfs *)malloc(sizeof(struct statfs) * nfs);
|
|
|
|
if (buf == NULL) {
|
|
|
|
perror("get_space");
|
|
|
|
return (DISK_DRIVE_INVALID);
|
|
|
|
}
|
|
|
|
nfs = getfsstat(buf, sizeof(struct statfs) * nfs, MNT_WAIT);
|
|
|
|
|
|
|
|
for (i = 0; i < nfs; ++i) {
|
|
|
|
if (strncmp(buf[i].f_mntonname, (char *)d->path, strlen(buf[i].f_mntonname)))
|
|
|
|
continue;
|
|
|
|
if (me && strlen(me->f_mntonname) > strlen(buf[i].f_mntonname))
|
|
|
|
continue;
|
|
|
|
me = buf + i;
|
|
|
|
}
|
|
|
|
if (!me) {
|
|
|
|
free(buf);
|
|
|
|
return (3);
|
|
|
|
}
|
|
|
|
fs->bytes_sector = 512;
|
|
|
|
fs->sectors_cluster = me->f_bsize / fs->bytes_sector;
|
|
|
|
fs->total_clusters = me->f_blocks / fs->sectors_cluster;
|
|
|
|
while (fs->total_clusters > 0xFFFF) {
|
|
|
|
fs->sectors_cluster *= 2;
|
|
|
|
fs->total_clusters = me->f_blocks / fs->sectors_cluster;
|
|
|
|
}
|
|
|
|
fs->avail_clusters = me->f_bavail / fs->sectors_cluster;
|
|
|
|
free(buf);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
DIR *dp = 0;
|
|
|
|
u_char searchdir[1024];
|
|
|
|
u_char *searchend;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert a dos filename into normal form (8.3 format, space padded)
|
|
|
|
*/
|
2001-08-03 10:47:56 +00:00
|
|
|
static void
|
1997-08-09 01:43:15 +00:00
|
|
|
to_dos_fcb(u_char *p, u_char *expr)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (expr[0] == '.') {
|
|
|
|
p[0] = '.';
|
|
|
|
if (expr[1] == '\0') {
|
|
|
|
for (i = 1; i < 11; i++)
|
|
|
|
p[i] = ' ';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (expr[1] == '.') {
|
|
|
|
p[1] = '.';
|
|
|
|
if (expr[2] == '\0') {
|
|
|
|
for (i = 2; i < 11; i++)
|
|
|
|
p[i] = ' ';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 8; i > 0; i--) {
|
|
|
|
switch (*expr) {
|
|
|
|
case '\0':
|
|
|
|
case '.':
|
|
|
|
for (; i > 0; i--)
|
|
|
|
*p++ = ' ';
|
|
|
|
break;
|
|
|
|
case '*':
|
|
|
|
for (; i > 0; i--)
|
|
|
|
*p++ = '?';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (islower(*expr)) {
|
|
|
|
*p++ = toupper(*expr++);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case '?':
|
|
|
|
*p++ = *expr++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (*expr != '\0' && *expr != '.')
|
|
|
|
++expr;
|
|
|
|
if (*expr)
|
|
|
|
++expr;
|
|
|
|
|
|
|
|
for (i = 3; i > 0; i--) {
|
|
|
|
switch (*expr) {
|
|
|
|
case '\0':
|
|
|
|
case '.':
|
|
|
|
for (; i > 0; i--)
|
|
|
|
*p++ = ' ';
|
|
|
|
break;
|
|
|
|
case '*':
|
|
|
|
for (; i > 0; i--)
|
|
|
|
*p++ = '?';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (islower(*expr)) {
|
|
|
|
*p++ = toupper(*expr++);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case '?':
|
|
|
|
*p++ = *expr++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** DOS can't handle multiple concurrent searches, and if we leave the
|
|
|
|
** search instance in the DTA we get screwed as soon as someone starts lots
|
|
|
|
** of searches without finishing them properly.
|
|
|
|
** We allocate a single search structure, and recycle it if find_first()
|
|
|
|
** is called before a search ends.
|
|
|
|
*/
|
2001-08-08 10:58:50 +00:00
|
|
|
static search_t dir_search;
|
1997-08-09 01:43:15 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the first file on drive which matches the path with the given
|
|
|
|
* attributes attr.
|
|
|
|
* If found, the result is placed in dir (32 bytes).
|
|
|
|
* The DTA is populated as required by DOS, but the state area is ignored.
|
|
|
|
* Returns DOS errno on failure.
|
|
|
|
*/
|
2001-07-24 11:50:23 +00:00
|
|
|
int
|
1997-08-09 01:43:15 +00:00
|
|
|
find_first(u_char *path, int attr, dosdir_t *dir, find_block_t *dta)
|
|
|
|
{
|
2001-07-24 11:50:23 +00:00
|
|
|
u_char new_path[1024], real_path[1024];
|
1997-08-09 01:43:15 +00:00
|
|
|
u_char *expr, *slash;
|
|
|
|
int drive;
|
|
|
|
int error;
|
|
|
|
search_t *search = &dir_search;
|
|
|
|
|
2001-07-31 11:27:48 +00:00
|
|
|
debug(D_REDIR, "find_first(%s, %x, %x)\n", path, attr, (int)dta);
|
1997-08-09 01:43:15 +00:00
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
error = dos_makepath(path, new_path);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (error)
|
|
|
|
return (error);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
expr = new_path;
|
1997-08-09 01:43:15 +00:00
|
|
|
slash = 0;
|
|
|
|
while (*expr != '\0') {
|
|
|
|
if (*expr == '\\' || *expr == '/')
|
|
|
|
slash = expr;
|
|
|
|
expr++;
|
|
|
|
}
|
|
|
|
*slash++ = '\0';
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
error = dos_to_real_path(new_path, real_path, &drive);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (error)
|
|
|
|
return (error);
|
|
|
|
|
|
|
|
if (attr == VOLUME_LABEL) /* never find a volume label */
|
|
|
|
return (NO_MORE_FILES);
|
|
|
|
|
|
|
|
if (search->dp) /* stale search? */
|
|
|
|
closedir(search->dp);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
search->dp = opendir(real_path);
|
1997-08-09 01:43:15 +00:00
|
|
|
if (search->dp == NULL)
|
|
|
|
return (PATH_NOT_FOUND);
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
ustrncpy(search->searchdir, real_path, 1024 - ustrlen(real_path));
|
1997-08-09 01:43:15 +00:00
|
|
|
search->searchend = search->searchdir;
|
|
|
|
while (*search->searchend)
|
|
|
|
++search->searchend;
|
|
|
|
*search->searchend++ = '/';
|
|
|
|
|
|
|
|
search->dp->dd_fd = squirrel_fd(search->dp->dd_fd);
|
|
|
|
|
|
|
|
dta->drive = drive | 0x80;
|
|
|
|
to_dos_fcb(dta->pattern, slash);
|
|
|
|
dta->flag = attr;
|
|
|
|
|
|
|
|
return (find_next(dir, dta));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Continue on where find_first left off.
|
|
|
|
* The results will be placed in dir.
|
|
|
|
* DTA state area is ignored.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
find_next(dosdir_t *dir, find_block_t *dta)
|
|
|
|
{
|
|
|
|
search_t *search = &dir_search;
|
|
|
|
struct dirent *d;
|
|
|
|
struct stat sb;
|
|
|
|
u_char name[16];
|
|
|
|
|
|
|
|
if (!search->dp)
|
|
|
|
return (NO_MORE_FILES);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
debug(D_REDIR, "find_next()\n");
|
|
|
|
#endif
|
|
|
|
|
2001-07-24 11:50:23 +00:00
|
|
|
while ((d = readdir(search->dp)) != 0) {
|
1997-08-09 01:43:15 +00:00
|
|
|
real_to_dos((u_char *)d->d_name, name);
|
|
|
|
to_dos_fcb(dir->name, name);
|
|
|
|
#if 0
|
|
|
|
printf("find_next: |%-11.11s| |%-11.11s| |%s| |%s|\n", dta->pattern, dir->name, d->d_name, name);
|
|
|
|
#endif
|
|
|
|
if (dos_match(dta->pattern, dir->name) == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ustrcpy(search->searchend, (u_char *)d->d_name);
|
|
|
|
if (ustat(search->searchdir, &sb) < 0)
|
|
|
|
continue;
|
|
|
|
#if 0
|
|
|
|
printf("find_next: %x\n", sb.st_mode);
|
|
|
|
#endif
|
|
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
|
|
if (!(dta->flag & DIRECTORY)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dir->attr = (S_ISDIR(sb.st_mode) ? DIRECTORY : 0) |
|
|
|
|
(uaccess(search->searchdir, W_OK) < 0 ? READ_ONLY_FILE : 0);
|
|
|
|
encode_dos_file_time(sb.st_mtime, &dir->date, &dir->time);
|
|
|
|
dir->start = 1;
|
|
|
|
dir->size = sb.st_size;
|
|
|
|
#if 0
|
|
|
|
printf("find_next: found %s\n",name);
|
|
|
|
#endif
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
closedir(search->dp);
|
|
|
|
search->dp = NULL;
|
|
|
|
return (NO_MORE_FILES);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* perfrom hokey DOS pattern matching. pattern may contain the wild cards
|
|
|
|
* '*' and '?' only. Follow the DOS convention that '?*', '*?' and '**' all
|
|
|
|
* are the same as '*'. Also, allow '?' to match the blank padding in a
|
|
|
|
* name (hence, ???? matchs all of "a", "ab", "abc" and "abcd" but not "abcde")
|
|
|
|
* Return 1 if a match is found, 0 if not.
|
|
|
|
*
|
|
|
|
* XXX This appears to be severely busted! (no * handling - normal?)
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
dos_match(u_char *pattern, u_char *string)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the base part first
|
|
|
|
*/
|
|
|
|
for (i = 11; i > 0; i--) {
|
|
|
|
if (*pattern != '?' && *string != *pattern)
|
|
|
|
return (0);
|
|
|
|
pattern++, string++;
|
|
|
|
}
|
|
|
|
return (1);
|
|
|
|
}
|