/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <utils.h>

#include "fdpool.h"

#define INVALID_FD (-1)
#define FDPOOL_SIZE 4

static struct pooled_fd fdpool_head = {
	.fd = INVALID_FD,
	.prev = &fdpool_head,
	.next = &fdpool_head
};
static int fdpool_count = 0;

static void fdpool_insert_head(struct pooled_fd *node)
{
	struct pooled_fd *prev = &fdpool_head;
	struct pooled_fd *next = prev->next;

	assert(node);

	prev->next = node;
	node->prev = prev;
	node->next = next;
	next->prev = node;

	fdpool_count++;
}

static void fdpool_remove(struct pooled_fd *node)
{
	struct pooled_fd *prev = node->prev;
	struct pooled_fd *next = node->next;

	assert(prev);
	assert(next);

	prev->next = next;
	next->prev = prev;

	fdpool_count--;
}

static struct pooled_fd *fdpool_remove_tail(void)
{
	struct pooled_fd *tail = fdpool_head.prev;

	assert(tail != &fdpool_head);

	fdpool_remove(tail);

	return tail;
}

static void fdpool_clear(struct pooled_fd *pfd)
{
	assert(pfd);

	pfd->fd = INVALID_FD;
	pfd->prev = pfd->next = NULL;
}

static void fdpool_unpool(struct pooled_fd *pfd)
{
	close(pfd->fd);
	fdpool_clear(pfd);
}

static void fdpool_evict(void)
{
	struct pooled_fd *tail;

	tail = fdpool_remove_tail();
	fdpool_unpool(tail);
}

static void fdpool_pool(struct pooled_fd *pfd, int fd)
{
	if (fdpool_count >= FDPOOL_SIZE)
		fdpool_evict();

	fdpool_insert_head(pfd);
	pfd->fd = fd;
}

static void fdpool_touch(struct pooled_fd *pfd)
{
	fdpool_remove(pfd);
	fdpool_insert_head(pfd);
}



void fdpool_init(struct pooled_fd *pfd)
{
	fdpool_clear(pfd);
}

int fdpool_open(struct pooled_fd *pfd, const char *pathname, int flags)
{
	int open_errno;
	int fd;

	if (pfd->fd != INVALID_FD) {
		fdpool_touch(pfd);
		return pfd->fd;
	}

	fd = open(pathname, flags);
	open_errno = errno;

	if (fd >= 0) {
		fdpool_pool(pfd, fd);
	}

	errno = open_errno;
	return fd;
}

void fdpool_close(struct pooled_fd *pfd)
{
	assert(pfd);

	fdpool_unpool(pfd);
}