/* pc_inode.c - assign the same unique st_ino to a file name
   and its file descriptor.
   If the file is a symlink, it is resolved and the unique st_ino and fd
   is always assigned to the real file name only.
   Copyright (C) 2010-2013 Free Software Foundation, Inc.

   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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Written by Juan M. Guerrero <juan.guerrero@gmx.de>,  2010-05-08.  */


#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#if __DJGPP__ == 2 && __DJGPP_MINOR__ > 3
# include <libc/symlink.h>
#endif


#if defined (__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8))
# define __gnuc_extension__  __extension__
#else
# define __gnuc_extension__
#endif

#define MAP_S_IFREG(file_type)               \
 (__gnuc_extension__                         \
   ({                                        \
      if (((file_type) & S_IFMT) == 0x0000)  \
        (file_type) |= S_IFREG;              \
      (file_type);                           \
   })                                        \
 )


void *xmalloc(size_t size);
void *xfree(void *pointer);

typedef struct inode_table_entry_tag *entry_ptr;
typedef struct inode_table_entry_tag {
  ino_t      inode_number;
  int        fd;
  char      *file_name;
  entry_ptr  next_inode;
} inode_table_entry;

static entry_ptr first_inode = NULL;


const char *resolve_symlink(const char *link_name)
{
#if __DJGPP__ == 2 && __DJGPP_MINOR__ > 3
  char *name = xmalloc(FILENAME_MAX * sizeof(char));
  const char *file_name = link_name;
  const int previous_errno = errno;


  if (__solve_symlinks(link_name, name))
    file_name = name;
  else
    xfree(name);
  errno = previous_errno;

  return file_name;
#else
  return link_name;
#endif
}


ino_t create_inode(const char *file_name, const int fd)
{
  int length;
  bool file_name_found;
  entry_ptr last_inode, inode;


  if (!first_inode)
  {
    first_inode = xmalloc(sizeof(inode_table_entry));
    first_inode->inode_number = 0;
    first_inode->fd = -1;
    first_inode->file_name = NULL;
    first_inode->next_inode = NULL;
  }
  inode = first_inode;


  /*
   *  Check if file name has already been registred.
   */
  for (file_name_found = true, last_inode = inode; inode && inode->file_name; last_inode = inode, inode = inode->next_inode, file_name_found = true)
  {
    int i;

    for (i = 0; inode->file_name[i] && file_name[i]; i++)
      if (!filename_char_eq(inode->file_name[i], file_name[i]))
      {
        file_name_found = false;
        break;
      }

    if (file_name_found && inode->file_name[i] == file_name[i])
    {
      if (inode->fd == -1)
        inode->fd = fd;
      return inode->inode_number;
    }
  }


  /*
   *  File name has not already been registered
   *  so allocate a new entry in the list and
   *  return the list index as inode number.
   */
  for (length = 0; file_name[length]; length++)
    ;
  length++;

  if (!inode)
  {
    inode = xmalloc(sizeof(inode_table_entry));
    last_inode->next_inode = inode;
  }
  inode->next_inode = NULL;
  inode->inode_number = last_inode->inode_number + 1;
  inode->fd = fd;
  inode->file_name = xmalloc(length * sizeof(char));
  memcpy(inode->file_name, file_name, length);
 
  return inode->inode_number;
}


ino_t get_inode(const int fd)
{
  entry_ptr inode;


  for (inode = first_inode; inode; inode = inode->next_inode)
    if (inode->fd == fd)
      return inode->inode_number;

  return (ino_t)(-1);
}


void replace_inode(const char *old_name, const char *new_name)
{
  int length;
  char *name = NULL;
  bool file_name_found;
  entry_ptr last_inode, inode;


  /*
   *  If new name has been registred, delete its inode entry from the table.
   */
  for (last_inode = inode = first_inode; inode && inode->file_name; last_inode = inode, inode = inode->next_inode)
  {
    int i;

    for (file_name_found = true, i = 0; inode->file_name[i] && new_name[i]; i++)
      if (!filename_char_eq(inode->file_name[i], new_name[i]))
      {
        file_name_found = false;
        break;
      }

    if (file_name_found && inode->file_name[i] == new_name[i])
    {
      if (last_inode == inode)
        first_inode = inode->next_inode;
      else
        last_inode->next_inode = inode->next_inode;
      name = inode->file_name;
      xfree(inode);
      break;
    }
  }


  /*
   *  If old name has been registred, rename it to new name.
   */
  for (file_name_found = true, last_inode = inode = first_inode; inode && inode->file_name; last_inode = inode, inode = inode->next_inode, file_name_found = true)
  {
    int i;

    for (i = 0; inode->file_name[i] && old_name[i]; i++)
      if (!filename_char_eq(inode->file_name[i], old_name[i]))
      {
        file_name_found = false;
        break;
      }

    if (file_name_found && inode->file_name[i] == old_name[i])
    {
      xfree(inode->file_name);

      if (name)
        inode->file_name = name;
      else
      {
        int length;

        for (length = 0; new_name[length]; length++)
         ;
        length++;

        inode->file_name = xmalloc(length * sizeof(char));
        memcpy(inode->file_name, new_name, length);
      }

      return;
    }
  }

  /*
   *  New name has not already been registered
   *  so allocate a new entry in the list for it.
   */
  for (length = 0; new_name[length]; length++)
    ;
  length++;

  inode = xmalloc(sizeof(inode_table_entry));
  inode->next_inode = NULL;
  inode->inode_number = last_inode->inode_number + 1;
  inode->fd = -1;
  inode->file_name = xmalloc(length * sizeof(char));
  memcpy(inode->file_name, new_name, length);

  last_inode->next_inode = inode;
}


void remove_inode(const char *file_name)
{
  bool file_name_found;
  entry_ptr last_inode, inode;


  for (last_inode = inode = first_inode; inode && inode->file_name; last_inode = inode, inode = inode->next_inode)
  {
    int i;

    for (file_name_found = true, i = 0; inode->file_name[i] && file_name[i]; i++)
      if (!filename_char_eq(inode->file_name[i], file_name[i]))
      {
        file_name_found = false;
        break;
      }

    if (file_name_found && inode->file_name[i] == file_name[i])
    {
      if (last_inode == inode)
        first_inode = inode->next_inode;
      else
        last_inode->next_inode = inode->next_inode;
      xfree(inode->file_name);
      xfree(inode);
      break;
    }
  }
}


void reset_descriptor(int fd)
{
  entry_ptr inode;


  for (inode = first_inode; inode; inode = inode->next_inode)
    if (inode->fd == fd)
    {
      inode->fd = -1;
      return;
    }
}




/*  Use DJGPP's stat function in stat_wrapper.  */
#undef stat

int djgpp_wrapper_stat(const char *file_name, struct stat *stat_buf)
{
  int status;


  status = stat(file_name, stat_buf);
  stat_buf->st_ino = create_inode(resolve_symlink(file_name), -1);
  stat_buf->st_mode = MAP_S_IFREG(stat_buf->st_mode);

  return status;
}


#if __DJGPP__ == 2 && __DJGPP_MINOR__ > 3
/*  Use DJGPP's lstat function in stat_wrapper.  */
#undef lstat

int djgpp_wrapper_lstat(const char *file_name, struct stat *stat_buf)
{
  int status;


  status = lstat(file_name, stat_buf);
  stat_buf->st_ino = create_inode(resolve_symlink(file_name), -1);
  stat_buf->st_mode = MAP_S_IFREG(stat_buf->st_mode);

  return status;
}
#endif


/*  Use DJGPP's fstat function in fstat_wrapper.  */
#undef fstat

int djgpp_wrapper_fstat(int fd, struct stat *stat_buf)
{
  int status;
  ino_t inode_number;


  status = fstat(fd, stat_buf);
  if (isatty(fd))
    return status;

  if ((inode_number = get_inode(fd)) != (ino_t)(-1))
    stat_buf->st_ino = inode_number;
  stat_buf->st_mode = MAP_S_IFREG(stat_buf->st_mode);

  return status;
}


/*  Use DJGPP's open function in open_wrapper.  */
#undef open
int open(const char *_path, int _oflag, ...);

int djgpp_wrapper_open(const char *file_name, int mode, ...)
{
  int fd, permissions = *(&mode + 1);
  const char *real_name = resolve_symlink(file_name);


  fd = open(real_name, mode, permissions);
  create_inode(real_name, fd);

  return fd;
}


/*  Use DJGPP's close function in close_wrapper.  */
#undef close
int close(int _fildes);

int djgpp_wrapper_close(int fd)
{
  int status;


  status = close(fd);
  reset_descriptor(fd);

  return status;
}


/*  Use DJGPP's fopen function in fopen_wrapper.  */
#undef fopen

FILE *djgpp_wrapper_fopen(const char *file_name, const char *mode)
{
  FILE *fp;
  const char *real_name = resolve_symlink(file_name);


  fp = fopen(real_name, mode);
  if (fp)
    create_inode(real_name, fileno(fp));

  return fp;
}


/*  Use DJGPP's fclose function in fclose_wrapper.  */
#undef fclose

int djgpp_wrapper_fclose(FILE *fp)
{
  int fd, status;


  fd = fileno(fp);
  status = fclose(fp);
  reset_descriptor(fd);

  return status;
}


/*  Use DJGPP's rename function in rename_wrapper.  */
#undef rename

int djgpp_wrapper_rename(const char *old_name, const char *new_name)
{
  int status;


  status = rename(old_name, new_name);
  if (!status)
    replace_inode(old_name, new_name);

  return status;
}


/*  Use DJGPP's unlink function in unlink_wrapper.  */
#undef unlink
int unlink(const char *_path);

int djgpp_wrapper_unlink(const char *file_name)
{
  int status;


  status = unlink(file_name);
  if (!status)
    remove_inode(file_name);

  return status;
}
