/*
 *  file.c -- ファイル名の処理、ワイルドカード、タイムスタンプ、などなど
 *
 *  revision history
 *	0.0: Jun.  8, 1996 by Dai ISHIJIMA (filemisc.c)
 *	0.1: Jul. 27, 1996
 *	0.2: Aug. 13, 1996
 *	0.3: Sep.  3, 1996
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <time.h>
#include <dirent.h>

#ifdef NEEDVFSH
#include <sys/vfs.h>
#endif
#ifdef USESTATVFS
#include <sys/statvfs.h>
#endif

#include "bsdfiler.h"

#define WILDCARDS 0x01
#define EXTENSION 0x02
#define FILENAME  0x04
#define DIRECTORY 0x08
#define DRIVE     0x10


static char wildsearchdir[MAXLINE];

    
/* DOS風のタイムスタンプを返す */
unsigned long tmstamp(char *name)
{
    struct stat sb;
    time_t clock;
    struct tm *p;
    stamp_t stamp;

    stat(name, &sb);
    clock = sb.st_mtime;
    p = localtime(&clock);
    stamp.dos.sec = p->tm_sec / 2;
    stamp.dos.min = p->tm_min;
    stamp.dos.hour = p->tm_hour;
    stamp.dos.day = p->tm_mday;
    stamp.dos.mon = p->tm_mon + 1;
    stamp.dos.year = p->tm_year + 1900 - 1980;
    return(stamp.val);
}


/* ファイルのサイズを返す */
long filelength(char *name)
{
    struct stat sb;

    stat(name, &sb);
    return(sb.st_size);
}


/* ファイルのタイプを返す (00: 普通のファイル, 02: ディレクトリ) */
int filetype(char *name)
{
    struct stat sb;

    stat(name, &sb);
    if ((sb.st_mode & S_IFMT) == S_IFDIR) {
	return(ISDIR);
    }
    else if ((sb.st_mode & S_IFMT) == S_IFREG) {
	return(ISREG);
    }
    else {
	return(-1);
    }
}


/* ディスク (カレントディレクトリ) の空き容量をバイト単位で返す */
long diskfree()
{
#ifdef USESTATVFS
    struct statvfs buf;

    statvfs(".", &buf);
#else
    struct statfs buf;

    statfs(".", &buf);
#endif
    return(buf.f_bavail * buf.f_bsize);
}


/* 文字列の中の大文字を小文字に変換する */
char *lowercase(char *s)
{
    char *t;

    t = s;
    while (*s != EOS) {
	if (('A' <= *s) && (*s <= 'Z')) {
	    *s += ('a' - 'A');
	}
	++s;
    }
    return(t);
}


/* 文字列の中の小文字を大文字に変換する */
char *uppercase(char *s)
{
    char *t;

    t = s;
    while (*s != EOS) {
	if (('a' <= *s) && (*s <= 'z')) {
	    *s -= ('a' - 'A');
	}
	++s;
    }
    return(t);
}


/* パス名を要素に分解する */
int fnsplit(char *path, char *drv, char *dir, char *name, char *ext)
{
    int status;
    int n, m;
    int colon, slash, dot;

    status = 0;
    colon = dot = slash = -1;
    n = 0;
    while (path[n] != EOS) {
	if ((path[n] == '*') || (path[n] == '?')) {
	    status |= WILDCARDS;
	}
	if (path[n] == ':') {
	    colon = n;
	    status |= DRIVE;
	}
	if ((path[n] == '\\') || (path[n] == '/')) {
	    slash = n;
	    status |= DIRECTORY;
	}
	if (path[n] == '.') {
	    dot = n;
	    status |= EXTENSION;
	}
	++n;
    }
    if (dot < 0) {
	dot = n;
    }
    m = 0;
    for (n = 0; n <= colon; n++) {
	drv[m] = path[n];
	++m;
    }
    drv[m] = EOS;
    m = 0;
    for (; n <= slash; n++) {
	dir[m] = path[n];
	++m;
    }
    dir[m] = EOS;
    m = 0;
    for (; n < dot; n++) {
	name[m] = path[n];
	++m;
    }
    name[m] = EOS;
    if (m != 0) {
	status |= FILENAME;
    }
    m = 0;
    for (; path[n] != EOS; n++) {
	ext[m] = path[n];
	++m;
    }
    ext[m] = EOS;
    return(status);
}


/* パスの区切りを '/' に、文字を小文字に */
char *upath(char *path)
{
    char *s;

    s = path;
    while (*s != EOS) {
	if (*s == DOSPATHSEP) {
	    *s = UNIXPATHSEP;
	}
	++s;
    }
    return(lowercase(path));
}


/* パスの区切りを '\' に、文字を大文字に */
char *dpath(char *path)
{
    char *s;

    s = path;
    while (*s != EOS) {
	if (*s == UNIXPATHSEP) {
	    *s = DOSPATHSEP;
	}
	++s;
    }
    return(uppercase(path));
}


/* DOS風のパスをUNIX風に変換する */
char *unixpath(char *path)
{
    static char s[MAXLINE];
    char drv[MAXLINE];
    char dir[MAXLINE];
    char nam[MAXLINE];
    char ext[MAXLINE];
    int n, m;

    fnsplit(path, drv, dir, nam, ext);
    strcpy(s, "./");
    if ((dir[0] == '/') || (dir[0] == '\\')) {
	strcat(s, &dir[1]);
    }
    else {
	strcat(s, dir);
    }
    strcat(s, nam);
    strcat(s, ext);
    return(upath(s));
}


/* UNIX風のパスをDOS風に変換する */
char *dospath(char *path)
{
    static char s[MAXLINE];

    if ((path[0] != '.') || (path[1] != '/')) {
	fprintf(stderr, "dospath(): conversion error\n");
    }
    strcpy(s, "c:");
    strcat(s, &path[1]);
    return(dpath(s));
}


/*
 * "MS-DOS in Your Palmtop PC", 25-3
 * "Valid Characters for File and Directory Names"
 */
/* ファイル名に使える文字 */
#define validchar(ch) ( \
    (('a' <= (ch)) && ((ch) <= 'z')) \
    || (('0' <= (ch)) && ((ch) <= '9')) \
    || ((ch) == '_') || ((ch) == '^') || ((ch) == '$') || ((ch) == '~') \
    || ((ch) == '!') || ((ch) == '#') || ((ch) == '%') || ((ch) == '&') \
    || ((ch) == '-') || ((ch) == '{') || ((ch) == '}') || ((ch) == '(') \
    || ((ch) == ')') || ((ch) == '@') || ((ch) == '\'') || ((ch) == '`') \
    || ((ch) == ' ') || ((ch) == '.') \
)

/* 予約ファイル名 */
char *rsvname[] = {
    "aux", "clock$", "com1", "com2", "com3", "com4",
    "con", "lpt1", "lpt2", "lpt3", "nul", "prn", NULL,
};


/* 予約ファイル名かどうかを確かめる */
int reserved(char *name)
{
    int n;

    for (n = 0; rsvname[n] != NULL; n++) {
	if (strcmp(name, rsvname[n]) == 0) {
	    break;
	}
    }
    return(rsvname[n] != NULL);
}


/* ファイル名を DOSスタイルの 8 + '.' + 3 文字に変換する */
char *dosname(char *name)
{
    static char s[FILNAMSIZ];
    int m, n;

    n = 0;
    for (m = 0; m < BASNAMSIZ; m++) {
	if ((name[n] == EOS) || (name[n] == '.')) {
	    break;
	}
	s[m] = name[n];
	++n;
    }
    if ((name[n] != EOS) && (name[n] != '.')) {
	return(NULL); /* ベースネームが 8文字以上 */
    }
    for (; m < BASNAMSIZ; m++) {
	s[m] = ' ';
    }
    s[m] = '.';
    ++m;
    if (name[n] == '.') {
	++n;
    }
    for (; m < FILNAMSIZ - 1; m++) {
	if (name[n] == EOS) {
	    break;
	}
	s[m] = name[n];
	++n;
    }
    if (name[n] != EOS) {
	return(NULL); /* 拡張子が 3文字以上 */
    }
    for (; m < FILNAMSIZ - 1; m++) {
	s[m] = ' ';
    }
    return(s);
}


/* ワイルドカードを展開する */
char *expandwild(char *wild)
{
    static char s[FILNAMSIZ];
    int m, n;

    n = 0;
    for (m = 0; m < BASNAMSIZ; m++) {
	if ((wild[n] == EOS) || (wild[n] == '.') || (wild[n] == '*')) {
	    break;
	}
	s[m] = wild[n];
	++n;
    }
    if ((wild[n] != '*') && (wild[n] != EOS) && (wild[n] != '.')) {
	return(NULL); /* ベースネームが 8文字以上 */
    }
    if (wild[n] == '*') { /* '*' を '?' のならびに変更 */
	for (; m < BASNAMSIZ; m++) {
	    s[m] = '?';
	}
	++n;
    }
    for (; m < BASNAMSIZ; m++) {
	s[m] = ' ';
    }
    s[m] = '.';
    ++m;
    if (wild[n] == '.') {
	++n;
    }
    for (; m < FILNAMSIZ - 1; m++) {
	if ((wild[n] == EOS) || (wild[n] == '*')) {
	    break;
	}
	s[m] = wild[n];
	++n;
    }
    if ((wild[n] != '*') && (wild[n] != EOS)) {
	return(NULL); /* 拡張子が 3文字以上 */
    }
    if (wild[n] == '*') {
	for (; m < FILNAMSIZ - 1; m++) {
	    s[m] = '?';
	}
    }
    for (; m < FILNAMSIZ - 1; m++) {
	s[m] = ' ';
    }
    return(s);
}


/* ファイルがワイルドカードにマッチするかを調べる */
int match(char *name, char *wildcard)
{
    char *s, *t;
    int i;

    if (name[0] == '.') { /* '.' で始まる名前ははねる */ 
	return(NO);
    }
    if ((s = dosname(name)) == NULL) { /* DOSの規則に合わない名前ははねる */
	return(NO);
    }
    if ((t = expandwild(wildcard)) == NULL) {
	return(NO);
    }
    for (i = 0; i < FILNAMSIZ - 1; i++) {
	if ((s[i] != t[i]) && (t[i] != '?')) {
	    break; /* マッチしない */
	}
	if (!(validchar(s[i]))) {
	    break; /* ファイル名に使えない文字を使っている */
	}
    }
    return(i == FILNAMSIZ - 1);
}


/* wildcard にマッチするファイル名を返す */
char **searchdir(char *wildcard,
                 char *entries[MAXDIRENT + 1],
		 char filnams[MAXDIRENT * FILNAMSIZ])
{
    DIR *dirp;
    struct dirent *dp;
    int i;
    char drv[MAXLINE];
    char dir[MAXLINE];
    char nam[MAXLINE];
    char ext[MAXLINE];
    char s[MAXLINE];
    
    fnsplit(wildcard, drv, dir, nam, ext);
    strcpy(s, lowercase(nam));
    strcat(s, lowercase(ext));
    strcpy(wildsearchdir, unixpath(dir));
    if ((dirp = opendir(wildsearchdir)) == NULL) {
	fprintf(stderr, "can't open directory %s\n", wildsearchdir);
	exit(1);
    }
    i = 0;
    while ((i < MAXDIRENT) && ((dp = readdir(dirp)) != NULL)) {
	if (!reserved(dp->d_name) && match(dp->d_name, s)) {
	    entries[i] = &filnams[FILNAMSIZ * i];
	    strcpy(entries[i], dp->d_name);
	    ++i;
	}
    }
    closedir(dirp);
    if (i == 0) {
	entries[i] = &filnams[0];
	strcpy(entries[i], ".");
	++i;
    }
    entries[i] = NULL;
    return(entries);
}


/* ファイルのタイプ、タイムスタンプ、サイズを返す */
lxdirent *direntry(char *name)
{
    static lxdirent entry;
    char path[MAXLINE];

    strcpy(path, wildsearchdir);
    strcat(path, name);
    entry.type = (filetype(path) == ISDIR) ? ENTDIR : ENTFIL;
    entry.timestamp = tmstamp(path);
    entry.size = filelength(path);
    strcpy(entry.name, uppercase(name));
    return(&entry);
}


/* ファイルを開く。もしエラーがあれば終了 */
FILE *efopen(char *path, char *mode)
{
    FILE *fp;
    
    if ((fp = fopen(path, mode)) == NULL) {
	fprintf(stderr, "can't open %s\n", path);
	exit(1);
    }
    return(fp);
}


/* カレントディレクトリを変更する */
int movdir(char *curdir, char *to)
{
    int n;

    if (to[1] == ':') {
	to += 2;
    }
    upath(to);
    if (to[0] == UNIXPATHSEP) { /* 絶対パス指定 */
	strcpy(curdir, unixpath(to));
    }
    else { /* 相対パス指定 */
	if (strcmp(to, "..") == 0) { /* 親ディレクトリへ */
	    if (strcmp(curdir, "./") == 0) {
		return(NO); /* ルートディレクトリからさらに上には行けない */
	    }
	    else {
		n = strlen(curdir) - 1;
		if (curdir[n] == UNIXPATHSEP) {
		    --n;
		}
		for (; n > 0; n--) {
		    if (curdir[n] == UNIXPATHSEP) {
			break;
		    }
		}
		curdir[n + 1] = EOS;
	    }
	}
	else { /* 子ディレクトリへ */
	    strcat(curdir, to);
	}
    }
    if (curdir[strlen(curdir) - 1] != UNIXPATHSEP) { /* パスの最後には */
	strcat(curdir, "/");                  /* 必ず '/' をつけておく */
    }
    if (filetype(curdir) != ISDIR) {
	return(NO);
    }
    return(YES);
}


/* クライアントからのパスをサーバのパスに変換 */
char *serverpath(char *curdir, char *path)
{
    static char s[MAXLINE];

    if (path[1] == ':') { /* ドライブが付いていたらスキップ */
	path += 2;
    }
    upath(path);
    if (path[0] == UNIXPATHSEP) { /* 絶対パス指定 */
	strcpy(s, unixpath(path));
    }
    else { /* 相対パス指定 */
	strcpy(s, curdir);
	strcat(s, unixpath(path));
    }
    return(s);
}
