
/*
    LinuxWare daemon - Netware like server for Linux

    Copyright (C) 1994, 1995  Ales Dryak <e-mail: A.Dryak@sh.cvut.cz>

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "mangle.h"
#include "logging.h"
#include "fsystem.h"

static vol_info volumes[MAX_VOLUME];
static vol_info root_volume={1,"","/"};
static dir_handle dirs[MAX_DIR]={{1,DH_PERMANENT,&root_volume,""},};
static file_handle files[MAX_FILE];
static search_handle search[MAX_SEARCH];

int alloc_file(connection* c,int fd,char* path)
{
	file_handle* f;
	
	if (fd<0) return -1;
	for(f=files;f<files+MAX_FILE;f++)
	{
		if (!f->alloc)
		{
			int* lfh;
			for(lfh=c->fh;lfh<c->fh+MAX_FILE_CONN;lfh++)
			{
				if (*lfh==-1)
				{
					*lfh=f-files;
					f->alloc=1;
					f->fd=fd;
					f->path[sizeof(f->path)-1]=0;
					strncpy(f->path,path,sizeof(f->path)-1);
					return lfh-c->fh;
				}
			}
			LPRINTF(LL_ERROR,("Cannot allocate new local file handle.\n"));
			return -1;
		}
	}
	LPRINTF(LL_ERROR,("Cannot allocate new global file handle.\n"));
	return -1;
}

file_handle* get_file_handle(connection* c,int lfh)
{
	int gfh;
	if (lfh<0 || lfh>=MAX_FILE_CONN) return NULL;
	gfh=c->fh[lfh];
	return (gfh>=0 && gfh<MAX_FILE && files[gfh].alloc) ? files+gfh : NULL;
}

void free_file(connection* c,int lfh)
{
	file_handle* f=get_file_handle(c,lfh);
	if (f==NULL) {LPRINTF(LL_ERROR,("Free bad file handle %i\n",lfh));return;}
	c->fh[lfh]=-1;
	close(f->fd);
	f->alloc=0;
}

int mount_volume(char* name,char* path)
{
	vol_info* vi;
	
	LPRINTF(LL_INFO,("mounting directory %s as volume %s\n",path,name));
	if (find_volume(name)!=NULL)
	{
		LPRINTF(LL_ERROR,("volume %s already mounted\n",name));
		return 0;
	}
	for(vi=volumes;vi<volumes+MAX_VOLUME;vi++)
	{
		if (!vi->mounted)
		{
			vi->mounted=1;
			strncpy(vi->name,name,sizeof(vi->name)-1);
			vi->name[sizeof(vi->name)-1]=0;
			strncpy(vi->path,path,sizeof(vi->path)-1);
			vi->path[sizeof(vi->path)-1]=0;
			return 1;
		}
	}
	LPRINTF(LL_ERROR,("no free volume table entry\n"));
	return 0;
}

vol_info* find_volume(char* name)
{
	vol_info* v;
	for(v=volumes;v<volumes+MAX_VOLUME;v++)
	{
		if (v->mounted && strcasecmp(name,v->name)==0) return v;
	}
	return NULL;
}

int get_volume_id(vol_info* vi)
{
	int res;
	
	if (vi==&root_volume) return ROOT_VOLUME_ID;
	res=vi-volumes;
	if (res<0 || res>=MAX_VOLUME) return -1;
	return res;
}

vol_info* get_volume(int id)
{
	if (id==ROOT_VOLUME_ID) return &root_volume;
	if (id<0 || id>=MAX_VOLUME) return NULL;
	return volumes+id;
}

char* unix2nw_path(char* dest,vol_info* vi,char* path)
{
	strcpy(dest,vi->name);
	strcat(dest,":");
	strcat(dest,path);
	return dest;
}

char* strunmanglecat(char* dest,char* src)
{
	char* p;
	char* q;
	char* r;
	char name[MAX_PATH];
	
	p=dest+strlen(dest);
	q=src;
	r=name;
	LPRINTF(LL_DEBUG,("strunmanglecat: %s %s\n",dest,src));
	for(;;q++)
	{
		if (*q=='/' || *q==0)
		{
			
			if (r>name)
			{
				*r=0;
				LPRINTF(LL_DEBUG,("strunmanglecat: unmangle %s %s\n",p,name));
				if (unmangle83(p,name)==NULL) return NULL;
				p=p+strlen(p);
			}
			*p++=*q;
			*p=0;
			if (*q==0) return dest;
			r=name;
		}
		else
		{
			*r++=*q;
		}
	}
}

char* nw2unix_path(char* dest,dir_handle* dh,char* path)
{
	vol_info* vi=NULL;

	*dest=0;
	LPRINTF(LL_DEBUG,("nw2unix: path '%s'\n",path));
	{
		char* p;
		for(p=path;*p;p++)
		{
			*p&=0x7F;
			if (*p=='\\') *p='/';
		}
		p--;
		if (*p=='/') *p=0;
	}
	if (dh==dirs) /* special: root handle */
	{
		char* p;
		for(p=path;*p;p++)
		{
			if (*p==':') break;
		}
		if (*p==0) return NULL;
		*p=0;
		LPRINTF(LL_DEBUG,("find_volume %s\n",path));
		vi=find_volume(path);
		*p=':';
		if (vi==NULL) return NULL;
		path=p+1;
#if 0
		if (*path=='/') path++;
#endif
		LPRINTF(LL_DEBUG,("path rest '%s'\n",path));
	}
	else
	{
		vi=dh->volume;
	}
	
	/* construct path */
	strcpy(dest,vi->path);
	if (*path=='/')
	{
		path++;
	}
	else
	{
		if (*(dh->path)!=0)
		{
			strcat(dest,"/");
			if (strunmanglecat(dest,dh->path)==NULL) return NULL;
		}
	}
	if (*path!=0)
	{
		strcat(dest,"/");
		if (strunmanglecat(dest,path)==NULL) return NULL;
	}
	LPRINTF(LL_DEBUG,("nw2unix: res: %s\n",dest));
	return dest;
}

int alloc_dir(connection* c,vol_info* vi,char* path,int mode)
{
	dir_handle* d;
	
	for(d=dirs;d<dirs+MAX_DIR;d++)
	{
		if (!d->alloc)
		{
			int* ldh;
			for(ldh=c->dh;ldh<c->dh+MAX_DIR_CONN;ldh++)
			{
				if (*ldh==-1)
				{
					*ldh=d-dirs;
					d->alloc=1;
					d->permanent=mode;
					d->volume=vi;
					strcpy(d->path,path);
					return ldh-c->dh;
				}
			}
			LPRINTF(LL_ERROR,("Cannot allocate new local dir handle.\n"));
			return -1;
		}
	}
	LPRINTF(LL_ERROR,("Cannot allocate new global dir handle.\n"));
	return -1;
}

dir_handle* get_dir_handle(connection* c,int ldh)
{
	int gdh;
	if (ldh<0 || ldh>=MAX_DIR_CONN) return NULL;
	gdh=c->dh[ldh];
	return (gdh>=0 && gdh<MAX_DIR && dirs[gdh].alloc) ? dirs+gdh : NULL;
}

void free_dir(connection* c,int ldh)
{
	dir_handle* d=get_dir_handle(c,ldh);
/*	int lsh;*/
	if (ldh==0) {LPRINTF(LL_ERROR,("Free root dir handle!\n"));return;}
	if (d==NULL) {LPRINTF(LL_ERROR,("Free bad dir handle %i\n",ldh));return;}
#if 0
	for(lsh=0;lsh<MAX_SEARCH_CONN;lsh++)
	{
		if (c->sh[lsh]!=-1)
		{
			search_handle* s=search+c->sh[lsh];
			if (s->alloc && s->dir==d)
			{
				free_search(c,lsh);
			}
		}
	}
#endif
	c->dh[ldh]=-1;
	d->alloc=0;
}

int alloc_search_local(connection* c,int gsh)
{
	int* lsh;
	
	c->stat_lsalloc++;
	for(lsh=c->sh;lsh<c->sh+MAX_SEARCH_CONN;lsh++)
	{
		if (*lsh==gsh)
		{
			c->stat_lshit++;
			return lsh-c->sh;
		}
	}
	for(lsh=c->sh;lsh<c->sh+MAX_SEARCH_CONN;lsh++)
	{
		if (*lsh==-1)
		{
			*lsh=gsh;
			search[gsh].alloc++;
			return lsh-c->sh;
		}
	}
	LPRINTF(LL_ERROR,("Cannot allocate new local search handle.\n"));
	return -1;
}

int alloc_search(connection* c,dir_handle* dh,char* search_path)
{
	search_handle* s;
	char path[MAX_PATH];

	c->stat_gsalloc++;	
	nw2unix_path(path,dh,search_path);
	for(s=search;s<search+MAX_SEARCH;s++)
	{
		if (s->alloc && strcmp(path,s->path)==0)
		{
			/* nice! */
			c->stat_gshit++;
			return alloc_search_local(c,s-search);
		}
	}
	for(s=search;s<search+MAX_SEARCH;s++)
	{
		if (!s->alloc)
		{
			memcpy(s->path,path,sizeof(s->path));
			return alloc_search_local(c,s-search);
		}
	}
	LPRINTF(LL_ERROR,("Cannot allocate new global search handle.\n"));
	return -1;
}

search_handle* get_search_handle(connection* c,int lsh)
{
	int gsh;
	if (lsh<0 || lsh>=MAX_SEARCH_CONN) return NULL;
	gsh=c->sh[lsh];
	return (gsh>=0 && gsh<MAX_SEARCH && search[gsh].alloc) ? search+gsh : NULL;
}

void free_search(connection* c,int lsh)
{
	search_handle* s=get_search_handle(c,lsh);
	if (s==NULL) {LPRINTF(LL_ERROR,("Free bad search handle %i\n",lsh));return;}
	c->sh[lsh]=-1;
	s->alloc--;
}

int mount_volumes(char* volfile)
{
	FILE* vol;
	char name[MAX_VOL_NAME+1];
	char path[MAX_PATH];
	int quit;
	
	vol=fopen(volfile,"rt");
	if (vol==NULL) {LPRINTF(LL_ERROR,("can't open volume file %s: %s\n",volfile,strerror(errno)));goto err_exit;}

	LPRINTF(LL_INFO,("reading volumes from %s\n",volfile));	
	for(quit=0;!quit;)
	{
		char* p;
		char c;
		
		for(p=name;p<name+MAX_VOL_NAME;p++)
		{
			c=fgetc(vol);
			if (c==EOF) {LPRINTF(LL_DEBUG,("unexpected EOF or read error: %s\n",strerror(errno))); goto err_exit;}
			if (isspace(c)) {*p=0;break;}
			*p=c;
		}
		if (p>=name+MAX_VOL_NAME) {name[MAX_VOL_NAME]=0;LPRINTF(LL_ERROR,("volume name too long: %s\n",name));goto err_exit;}
		for(;;)
		{
			c=fgetc(vol);
			if (c==EOF) {LPRINTF(LL_DEBUG,("unexpected EOF or read error: %s\n",strerror(errno))); goto err_exit;}
			if (!isspace(c)) break;
		}
		*path=c;
		for(p=path+1;p<path+MAX_PATH;p++)
		{
			int c=fgetc(vol);
			if (c==EOF) {LPRINTF(LL_DEBUG,("unexpected EOF or read error: %s\n",strerror(errno))); goto err_exit;}
			if (c=='\n') {*p=0;break;}
			*p=c;
		}
		if (p>=path+MAX_PATH) {path[MAX_PATH-1]=0;LPRINTF(LL_ERROR,("path too long: %s\n",path));goto err_exit;}
		if (*path!='/') {LPRINTF(LL_ERROR,("path should be absolute: %s\n",path));goto err_exit;}
		mount_volume(name,path);
		quit=feof(vol);
	}
	
	if (fclose(vol)!=0) {LPRINTF(LL_ERROR,("can't close passwd file %s: %s\n",volfile,strerror(errno)));goto err_exit;}
	return 1;
err_exit:
	if (vol!=NULL) fclose(vol);
	return 0;
}
	
void init_fsystem()
{
	dir_handle* d;
	file_handle* f;
	search_handle* s;
	vol_info* v;
	/* dirs[0] is special */
	for(d=dirs+1;d<dirs+MAX_DIR;d++)
	{
		d->alloc=0;
	}
	for(f=files;f<files+MAX_FILE;f++)
	{
		f->alloc=0;
	}
	for(s=search;s<search+MAX_SEARCH;s++)
	{
		s->alloc=0;
	}
	for(v=volumes;v<volumes+MAX_VOLUME;v++)
	{
		v->mounted=0;
	}
	mount_volumes(VOL_FILENAME);
	LPRINTF(LL_INFO,("sizeof(dirs)=%i sizeof(files)=%i sizeof(volumes)=%i\n",sizeof(dirs),sizeof(files),sizeof(volumes)));
	LPRINTF(LL_INFO,("fsystem initialized\n"));
}

void done_fsystem()
{
	dir_handle* d;
	file_handle* f;
	/* dirs[0] is special */
	for(d=dirs+1;d<dirs+MAX_DIR;d++)
	{
		if (d->alloc)
		{
			LPRINTF(LL_ERROR,("non deallocated global dir handle %i\n",d-dirs));
			d->alloc=0;
		}
	}
	for(f=files;f<files+MAX_FILE;f++)
	{
		if (f->alloc)
		{
			LPRINTF(LL_ERROR,("non deallocated global file handle %i\n",f-files));
			close(f->fd);
			f->alloc=0;
		}
	}
	LOG_START(LL_INFO)
	fprint_mangle_stat(log_file);
	LOG_END
	LPRINTF(LL_INFO,("fsystem shutdown\n"));
}

void fprint_fs_stat(FILE* file)
{
	int sa=0;
	int sal=0;
	search_handle* s;
	for(s=search;s<search+MAX_SEARCH;s++)
	{
		sal+=s->alloc;
		sa+=(s->alloc?1:0);
	}
	fprintf(file,"fs stat: search: alloc: %i locks: %i\n",sa,sal);
}
