// $Id: File.cc,v 1.14 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1996, 1997, 1998, 1999, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <sys/types.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include <sys/resource.h>
#include <errno.h>

#include "File.h"
#include "Type.h"

// The following could in principle be part of a "file manager" object.

#define MAX_OPEN_FILE_CACHE_SIZE 32
static int num_open_files = 0;
static int max_open_files = 0;
static BroFile* head = 0;
static BroFile* tail = 0;


// Maximizes the number of open file descriptors and returns the number
// that we should use for the cache.
static int maximize_num_fds()
	{
#ifdef NO_HAVE_SETRLIMIT
	return MAX_OPEN_FILE_CACHE_SIZE;
#else
	struct rlimit rl;
	if ( getrlimit(RLIMIT_NOFILE, &rl) < 0 )
		internal_error("maximize_num_fds(): getrlimit failed");

	if ( rl.rlim_max == RLIM_INFINITY )
		{
		// Don't try raising the current limit.
		if ( rl.rlim_cur == RLIM_INFINITY )
			// Let's not be too ambitious.
			return MAX_OPEN_FILE_CACHE_SIZE;
		else
			return rl.rlim_cur / 2;
		}

	// See if we can raise the current to the maximum.
	rl.rlim_cur = rl.rlim_max;

	if ( setrlimit(RLIMIT_NOFILE, &rl) < 0 )
		internal_error("maximize_num_fds(): setrlimit failed");

	return rl.rlim_cur / 2;
#endif
	}


BroFile::BroFile(FILE* arg_f)
	{
	f = arg_f;
	name = access = 0;
	t = base_type(TYPE_STRING);
	is_open = (f != 0);
	is_managed = 0;
	position = 0;
	next = prev = 0;
	}

BroFile::BroFile(const char* arg_name, const char* arg_access, BroType* arg_t)
	{
	name = copy_string(arg_name);
	access = copy_string(arg_access);
	t = arg_t ? arg_t : base_type(TYPE_STRING);
	position = 0;

	if ( ! max_open_files )
		// Haven't initialized yet.
		max_open_files = maximize_num_fds();

	if ( num_open_files >= max_open_files )
		PurgeCache();

	f = fopen(name, access);

	if ( f )
		{
		InsertAtBeginning();
		is_managed = 1;
		}
	else
		{ // No point managing it.
		is_open = is_managed = 0;
		error(fmt("cannot open %s: %s",name, strerror(errno)));
		}
	}

BroFile::~BroFile()
	{
	Close();
	Unref(t);

	delete [] name;
	delete [] access;
	}

FILE* BroFile::File()
	{
	if ( is_managed )
		{
		if ( is_open )
			MoveToBeginning();
		else
			{
			if ( f )
				internal_error("BroFile non-nil non-open file");

			if ( num_open_files >= max_open_files )
				PurgeCache();

			if ( position == 0 )
				// Need to truncate it.
				f = fopen(name, access);
			else
				// Don't clobber it.
				f = fopen(name, "a");

			if ( ! f )
				{
				f = fopen("/dev/null", "w");

				if ( ! f )
					internal_error("out of file descriptors");
				run_time("can't open %s", this);
				is_managed = 0;
				return f;
				}

			if ( fseek(f, position, SEEK_SET) < 0 )
				run_time("reopen seek failed", this);

			InsertAtBeginning();
			}
		}

	return f;
	}

FILE* BroFile::Seek(long new_position)
	{
	if ( ! File() )
		return 0;

	if ( fseek(f, new_position, SEEK_SET) < 0 )
		run_time("seek failed", this);

	return f;
	}

void BroFile::SetBuf(bool buffered)
	{
	if ( ! f )
		return;

	if ( setvbuf(f, NULL, buffered ? _IOFBF : _IOLBF, 0) != 0 )
		run_time("setvbuf failed", this);
	}

int BroFile::Close()
	{
	// Do not close stdout/stderr.
	if ( f == stdout || f == stderr )
		return 0;

	if ( is_managed )
		{
		Unlink();
		if ( f )
			{
			fclose(f);
			f = 0;
			}

		is_managed = 0;	// no longer managed since will never reopen

		return 1;
		}

	// Not managed.
	if ( ! f )
		return 0;

	fclose(f);
	f = 0;
	return 1;
	}

void BroFile::Suspend()
	{
	if ( ! is_managed )
		internal_error("BroFile::Suspend() called for non-managed file");
	if ( ! is_open )
		internal_error("BroFile::Suspend() called for non-open file");
	Unlink();

	if ( ! f )
		internal_error("BroFile::Suspend() called for nil file");

	if ( (position = ftell(f)) < 0 )
		{
		run_time("ftell failed", this);
		position = 0;
		}

	fclose(f);
	f = 0;
	}

void BroFile::PurgeCache()
	{
	if ( ! tail )
		internal_error("BroFile purge of empty cache");

	tail->Suspend();
	}

void BroFile::Unlink()
	{
	if ( is_open )
		{
		if ( head == this )
			head = Next();
		else
			Prev()->SetNext(next);

		if ( tail == this )
			tail = Prev();
		else
			Next()->SetPrev(prev);

		if ( (head || tail) && ! (head && tail) )
			internal_error("BroFile link list botch");

		is_open = 0;
		prev = next = 0;

		if ( --num_open_files < 0 )
			internal_error("BroFile open files underflow");
		}
	}

void BroFile::InsertAtBeginning()
	{
	if ( ! head )
		{
		head = tail = this;
		next = prev = 0;
		}
	else
		{
		SetNext(head);
		SetPrev(0);
		head->SetPrev(this);
		head = this;
		}

	is_open = 1;

	if ( ++num_open_files > max_open_files )
		internal_error("BroFile open files overflow");
	}

void BroFile::MoveToBeginning()
	{
	if ( head == this )
		return;	// already at the beginning

	if ( ! is_open || ! prev )
		internal_error("BroFile inconsistency in MoveToBeginning()");

	Unlink();
	InsertAtBeginning();
	}

void BroFile::Describe(ODesc* d) const
	{
	d->AddSP("file");

	if ( name )
		{
		d->Add("\"");
		d->Add(name);
		d->AddSP("\"");
		}

	d->AddSP("of");
	t->Describe(d);
	}
