/* $Id: file.c,v 1.55 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1998-2000 Atsushi Onoe
 * 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

#include "cue.h"
#include "tarfile.h"

static struct filedb *cache, *tail;
static int ncache;

static void
fdb_unlink(struct filedb *fdb)
{
	if (fdb->next)
		fdb->next->prev = fdb->prev;
	else
		tail = fdb->prev;
	if (fdb->prev)
		fdb->prev->next = fdb->next;
	else
		cache = fdb->next;
}

static void
fdb_linkhead(struct filedb *fdb)
{
	fdb->next = cache;
	fdb->prev = NULL;
	if (cache)
		cache->prev = fdb;
	else
		tail = fdb;
	cache = fdb;
}

static void
fdb_linktail(struct filedb *fdb)
{
	fdb->next = NULL;
	fdb->prev = tail;
	if (tail)
		tail->next = fdb;
	else
		cache = fdb;
	tail = fdb;
}

void
fdb_clear(struct filedb *fdb)
{
	struct linedb *ldb, *nldb;
	struct kbuf *kb, *nkb;
	int mainpart;

	mainpart = !(fdb->flags & FDB_SUBPART);

	if (mainpart)
		fdb_unlink(fdb);
	if (fdb->nextpart)
		fdb_clear(fdb->nextpart);

	if (mainpart && fdb->name)
		free(fdb->name);
	if (fdb->hdr)
		free(fdb->hdr);
	for (ldb = fdb->lhead; ldb; ldb = nldb) {
		nldb = ldb->next;
		free(ldb);
	}
	for (kb = fdb->kbuf; kb; kb = nkb) {
		nkb = kb->next;
		free(kb);
	}
	if (mainpart) {
		if (CP(&fdb->mmap) != NULL && CL(&fdb->mmap) > 0) {
			if (fdb->flags & FDB_NOMMAP)
				free(CP(&fdb->mmap));
			else
				munmap(CP(&fdb->mmap), CL(&fdb->mmap));
		}
		memset(fdb, 0, sizeof(*fdb));
		fdb_linktail(fdb);
	} else {
		free(fdb);
	}
}

static struct filedb *
fdb_find(const char *name)
{
	struct filedb *fdb;

	for (fdb = cache; fdb; fdb = fdb->next) {
		if (fdb->name == NULL)
			return NULL;
		if (strcmp(fdb->name, name) == 0) {
			/* LRU */
			fdb_unlink(fdb);
			fdb_linkhead(fdb);
			break;
		}
	}
	return fdb;
}

void
fdb_purge(const char *name)
{
	struct filedb *fdb;

	for (fdb = cache; fdb && fdb->name; fdb = fdb->next)
		if (name == NULL || strcmp(fdb->name, name) == 0)
			fdb->flags |= FDB_PURGED;
}


struct filedb *
fdb_open(const char *namearg)
{
	char *name;
	struct filedb *fdb;
	int fd;
	struct stat stbuf;
	int (*func_read)(), (*func_fstat)(), (*func_close)();

	if ((fdb = fdb_find(namearg))) {
		if (!(fdb->flags & FDB_PURGED)) {
			if (fdb->flags & FDB_TARFILE)
				fd = tar_stat(namearg, &stbuf);
			else
				fd = stat(namearg, &stbuf);
			if (fd == 0
			&&  stbuf.st_ino == fdb->stbuf.st_ino
			&&  stbuf.st_mtime == fdb->stbuf.st_mtime
			&&  stbuf.st_size == fdb->stbuf.st_size)
				return fdb;
		}
		if ((name = strdup(namearg)) == NULL)
			return NULL;
		fdb_clear(fdb);
	} else {
		if ((name = strdup(namearg)) == NULL)
			return NULL;
	}

	if ((fd = open(name, O_RDONLY)) >= 0) {
#ifdef FILE_DEBUG
		func_read = read;
#else /* FILE_DEBUG */
		func_read = NULL;
#endif /* FILE_DEBUG */
		func_fstat = fstat;
		func_close = close;
	} else {
		if ((fd = tar_open(name)) == -1) {
			free(name);
			return NULL;
		}
		func_read = tar_read;
		func_fstat = tar_fstat;
		func_close = tar_close;
	}
	if (func_fstat(fd, &stbuf) < 0 || stbuf.st_size == 0) {
		func_close(fd);
		free(name);
		return NULL;
	}
	if (ncache >= FDB_CACHE) {
		fdb = tail;
		fdb_clear(fdb);
		fdb_unlink(fdb);
	} else {
		fdb = calloc(1, sizeof(*fdb));
		if (fdb == NULL) {
			func_close(fd);
			free(name);
			return NULL;
		}
		ncache++;
	}
	if (func_read == tar_read)
		fdb->flags |= FDB_TARFILE;
	fdb_linkhead(fdb);
	if (func_read != NULL) {
		if ((fdb->mmap.ptr = malloc(stbuf.st_size)) != NULL
		&&  func_read(fd, fdb->mmap.ptr, stbuf.st_size) == stbuf.st_size) {
			fdb->flags |= FDB_NOMMAP;
		} else {
			if (fdb->mmap.ptr)
				free(fdb->mmap.ptr);
			fdb->mmap.ptr = (caddr_t)-1;
		}
	} else {
		CSETP(&fdb->mmap, mmap(NULL, stbuf.st_size, PROT_READ, MAP_SHARED, fd, 0));
	}
	fdb->mmap.len = stbuf.st_size;
	func_close(fd);
	if (fdb->mmap.ptr == (char *)-1
	||  fdb->mmap.ptr == NULL) {
		if (fdb->mmap.ptr) {
			if (fdb->flags & FDB_NOMMAP)
				free(fdb->mmap.ptr);
			else
				munmap(fdb->mmap.ptr, fdb->mmap.len);
		}
		fdb_clear(fdb);
		free(name);
		return NULL;
	}
	fdb->name = name;
	fdb->stbuf = stbuf;
	fdb->ptr = CP(&fdb->mmap);
	fdb->eptr = CE(&fdb->mmap);
	return fdb;
}

struct filedb *
fdb_mkpart(ccbuf_t *buf)
{
	struct filedb *fdb;

	if ((fdb = calloc(1, sizeof(*fdb))) == NULL)
		return NULL;
	fdb->flags = FDB_SUBPART;
	fdb->ptr = CP(buf);
	fdb->eptr = CE(buf);
	return fdb;
}

void
fdb_replace(struct filedb *fdb, cbuf_t *buf)
{

	if (fdb->flags & FDB_SUBPART)
		return;
	if (CP(&fdb->mmap) != NULL && CL(&fdb->mmap) > 0) {
		if (fdb->flags & FDB_NOMMAP)
			free(CP(&fdb->mmap));
		else
			munmap(CP(&fdb->mmap), CL(&fdb->mmap));
	}
	fdb->mmap = *buf;
	fdb->ptr = CP(buf);
	fdb->eptr = CE(buf);
}

static void
fdb_addline(struct filedb *fdb, const char *str, int len, int more)
{
	struct linedb *ldb;
	ccbuf_t *line;
	int index;

	ldb = fdb->ltail;
	if (ldb == NULL || ldb->inuse >= LINEBLOCK) {
		index = ldb ? ldb->index : -1;
		ldb = malloc(sizeof(*ldb));
		ldb->next = NULL;
		ldb->prev = fdb->ltail;
		if (fdb->ltail)
			fdb->ltail->next = ldb;
		else
			fdb->lhead = ldb;
		fdb->ltail = ldb;
		ldb->index = ++index;
		ldb->inuse = 0;
		memset(ldb->more, 0, sizeof(ldb->more));
	}
	if (more)
		ldb->more[ldb->inuse / 8] |= ((u_char)1 << ldb->inuse % 8);
	line = &ldb->line[ldb->inuse++];
	line->ptr = str;
	line->len = len;
	fdb->lines++;
}

void
fdb_getline(struct filedb *fdb, ccbuf_t *buf)
{
	const char *s, *p, *ep;
	int w, noconv;

	s = p = buf->ptr;
	ep = p + buf->len;
	w = 0;
	noconv = (fdb->flags & FDB_NOCONV);
	while (p < ep) {
		switch (*(unsigned char *)p) {
		case '\n':
			fdb_addline(fdb, s, p - s, 0);
			p++;
			s = p;
			w = 0;
			continue;
		case '\t':
			w = (w / 8 + 1) * 8;
			p++;
			break;
		case CS_SS2:
			w++;
			p += 2;
			break;
		case CS_SS3:
			if (w + 1 >= LINE_WIDTH && !noconv) {
				w++;
				break;
			}
			w += 2;
			p += 3;
			break;
		case CS_SSX:
			if (p[1] & CS_DWIDTH) {
				if (w + 2 >= LINE_WIDTH && !noconv) {
					w++;
					break;
				}
				w += 2;
				p += 4;
			} else {
				w++;
				p += 3;
			}
			break;
		default:
			if (*p & 0x80) {
				if (w + 1 >= LINE_WIDTH && !noconv) {
					w++;
					break;
				}
				w += 2;
				p += 2;
			} else {
				p++;
				w++;
			}
			break;
		}
		if (w >= LINE_WIDTH && !noconv) {
			if (p == ep || *p == '\n') {
				fdb_addline(fdb, s, p - s, 0);
				if (p < ep)
					p++;
			} else
				fdb_addline(fdb, s, p - s, 1);
			s = p;
			w = 0;
		}
	}
	if (p > s)
		fdb_addline(fdb, s, p - s, 0);
}

void
fdb_reload(struct filedb *fdb, int linenum, const char *ptr)
{
	struct linedb *ldb, *nldb;
	int i;

	if (linenum >= fdb->lines)
		return;
	fdb->lines = linenum;
	for (ldb = fdb->lhead; linenum >= LINEBLOCK; ldb = ldb->next)
		linenum -= LINEBLOCK;
	ldb->inuse = linenum;
	for (i = linenum / 8 + 1; i < sizeof(ldb->more); i++)
		ldb->more[i] = 0;
	ldb->more[linenum / 8] &= (u_char)0xff >> (8 - linenum % 8);

	fdb->ltail = fdb->lcur = ldb;
	nldb = ldb->next;
	ldb->next = NULL;
	while ((ldb = nldb) != NULL) {
		nldb = ldb->next;
		free(ldb);
	}
	/* XXX kbuf should be freed */
	fdb->ptr = ptr;
}

ccbuf_t *
fdb_read(struct filedb *fdb, int linenum)
{
	struct linedb *ldb;
	cbuf_t buf;
	ccbuf_t *sep;
	const char *p, *savep, *ep;
	int i, st, issep;

	if (linenum < 0)
		return NULL;
	ep = fdb->eptr;
	p = fdb->ptr;
	sep = &fdb->separator;
	issep = MPART_NONE;
	if (sep->ptr == NULL)
		sep = NULL;

	/* XXX: special */
	if (fdb->lines == 0
	&&  fdb->prevpart != NULL
	&&  fdb->prevpart->flags & FDB_INLINE) {
		fdb_addline(fdb, "", 0, 0);
	}

	/* XXX: just in case */
	if (sep && (issep = mpart_issep(p, ep, sep)) != MPART_NONE) {
		fdb->flags |= FDB_EOF;
		message_multipart_next(fdb, issep);
	}

	while (linenum >= fdb->lines) {
		if (fdb->flags & FDB_EOF) {
			fdb->ptr = p;
			if (fdb->flags & FDB_INLINE) {
				linenum -= fdb->lines;
				while (fdb->nextpart != NULL) {
					fdb = fdb->nextpart;
					if (!(fdb->flags & FDB_HIDDEN))
						break;
				}
				return fdb_read(fdb, linenum + fdb->skip_lines);
			}
			return NULL;
		}
#if 0
		if (fdb->flags & FDB_NOTEXT) {
			buf.ptr = banner("NOTEXT");
			buf.len = strlen(buf.ptr);
			buf.ptr = copy_kbuf(&fdb->kbuf, buf.ptr, buf.len);
			fdb_getline(fdb, &buf);
			if (sep) {
				for (;;) {
					issep = mpart_issep(p, ep, sep);
					if (issep != MPART_NONE)
						break;
					while (p < ep) {
						if (*p++ == '\n')
							break;
					}
				}
				fdb->ptr = p;
				fdb->flags |= FDB_EOF;
				message_multipart_next(fdb, issep);
			} else
				p = ep;
			continue;
		}
#endif
		for (i = 0; i < LINEBLOCK; i++) {
			savep = p;
			st = decode_text(&p, ep, &buf, fdb->flags);
			if (st < 0 &&
			    (fdb->flags & FDB_CHARSET) != FDB_CS_UTF8 &&
			    (fdb->flags & FDB_CHARSET) != FDB_CS_GBK &&
			    !(fdb->flags & (FDB_NOCONV|FDB_NOTEXT))) {
				if ((fdb->flags & FDB_CHARSET) == FDB_CS_SJIS) {
					if (fdb->flags & FDB_GUESSED) {
						/* false assumption */
						fdb->flags &= ~FDB_CHARSET;
					}
				} else if (!(fdb->flags & FDB_GUESSED)) {
					p = savep;
					st = decode_text(&p, ep, &buf,
					    (fdb->flags & ~FDB_CHARSET) |
					    FDB_CS_SJIS);
					if (st > 0) {
						fdb->flags &= ~FDB_CHARSET;
						fdb->flags |= FDB_CS_SJIS | FDB_GUESSED;
					}
				}
				if ((fdb->flags & FDB_CHARSET) != FDB_CS_SJIS) {
					p = savep;
					fdb->flags |= FDB_NOTEXT;
					st = decode_text(&p, ep, &buf,
					    fdb->flags);
				}
			}
			if (st)
				buf.ptr = copy_kbuf(&fdb->kbuf, buf.ptr, buf.len);
			if (sep) {
				issep = mpart_issep(p, ep, sep);
				if (issep != MPART_NONE)
					break;
			}
			fdb_getline(fdb, (ccbuf_t *)&buf);
			if (p >= ep) {
				fdb->flags |= FDB_EOF;
				break;
			}
		}
		if (issep != MPART_NONE) {
			if (buf.len > 0 && p[-1] == '\n') {
				p--;
				if (!(fdb->flags & FDB_ENC_B64))
					buf.len--;
			}
			if (buf.len > 0)
				fdb_getline(fdb, (ccbuf_t *)&buf);
			fdb->ptr = p;
			fdb->flags |= FDB_EOF;
			message_multipart_next(fdb, issep);
		}
	}
	fdb->ptr = p;
	if (fdb->lcur && linenum / LINEBLOCK == fdb->lcur->index)
		return &fdb->lcur->line[linenum % LINEBLOCK];
	for (ldb = fdb->lhead; linenum >= LINEBLOCK; ldb = ldb->next)
		linenum -= LINEBLOCK;
	fdb->lcur = ldb;
	return &ldb->line[linenum];
}

/* called only when fdb_read() was successful */
int
fdb_ismore(struct filedb *fdb, int linenum)
{
	struct linedb *ldb;

	if (linenum < 0)
		return 0;
	while (linenum >= fdb->lines) {
		if (fdb->flags & FDB_INLINE) {
			linenum -= fdb->lines;
			fdb = fdb->nextpart;
			linenum += fdb->skip_lines;
			continue;
		}
		return 0;
	}
	if (fdb->lcur && linenum / LINEBLOCK == fdb->lcur->index) {
		ldb = fdb->lcur;
		linenum %= LINEBLOCK;
	} else {
		for (ldb = fdb->lhead; linenum >= LINEBLOCK; ldb = ldb->next)
			linenum -= LINEBLOCK;
	}
	return ldb->more[linenum / 8] & ((u_char)1 << linenum % 8);
}

#ifdef TEST_MAIN
void
main(int argc, char **argv)
{
	struct filedb *fdb;
	cbuf_t *buf;
	int n;

	while (*++argv) {
		fdb = fdb_open(*argv);
		if (fdb == NULL) {
			printf("Cannot open %s\n", *argv);
			continue;
		}
		for (n = 0; (buf = fdb_read(fdb, n)); n++) {
			write(1, buf->ptr, buf->len);
			write(1, "\n", 1);
		}
	}
}
#endif /* TEST_MAIN */
