/* systems.h - Most of the system dependant code and defines are here. */

/* This file is part of GDBM, the GNU data base manager.
   Copyright (C) 1990, 1991, 1993, 2007, 2011, 2013 Free Software Foundation,
   Inc.

   GDBM 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 3, or (at your option)
   any later version.

   GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>.    */

/* Include all system headers first. */
#include <sys/types.h>
#include <stdio.h>
#if HAVE_SYS_FILE_H
# include <sys/file.h>
#endif
#include <sys/stat.h>
#include <stdlib.h>
#if HAVE_STRING_H
# include <string.h>
#else
# include <strings.h>
#endif
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifndef SEEK_SET
# define SEEK_SET   0
#endif

#ifndef O_CLOEXEC
# define O_CLOEXEC  0
#endif

/* Default block size.  Some systems do not have blocksize in their
   stat record. This code uses the BSD blocksize from stat. */

#ifndef __DJGPP__
#if HAVE_STRUCT_STAT_ST_BLKSIZE
# define STATBLKSIZE file_stat.st_blksize
#else
# define STATBLKSIZE 1024
#endif
#endif

/* Do we have ftruncate? */
#if HAVE_FTRUNCATE
# define TRUNCATE(dbf) ftruncate (dbf->desc, 0)
#else
# define TRUNCATE(dbf) close( open (dbf->name, O_RDWR|O_TRUNC, mode));
#endif

#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif

/* I/O macros. */
#if HAVE_MMAP
# define __read(_dbf, _buf, _size)	_gdbm_mapped_read(_dbf, _buf, _size)
# define __write(_dbf, _buf, _size)	_gdbm_mapped_write(_dbf, _buf, _size)
# define __lseek(_dbf, _off, _whn)	_gdbm_mapped_lseek(_dbf, _off, _whn)
# define __fsync(_dbf)			_gdbm_mapped_sync(_dbf)
#else
# define __read(_dbf, _buf, _size)	read(_dbf->desc, _buf, _size)
# define __write(_dbf, _buf, _size)	write(_dbf->desc, _buf, _size)
# define __lseek(_dbf, _off, _whn)	lseek(_dbf->desc, _off, _whn)
# if HAVE_FSYNC
#  define __fsync(_dbf)			fsync(_dbf->desc)
# else
#  define __fsync(_dbf)			{ sync(); sync(); }
# endif
#endif

/* For systems that distinguish between text and binary I/O.
   O_BINARY is usually declared in fcntl.h  */
#if !defined O_BINARY && defined _O_BINARY
  /* For MSC-compatible compilers.  */
# define O_BINARY _O_BINARY
#endif
#if O_BINARY
  /* Files must be opened in BINARY mode. */
# undef  open
# define open(file, mode, ...)  open((file), (mode) | O_BINARY, ##__VA_ARGS__)
# define IS_SLASH(c)            ((c) == '/' || (c) == '\\')
# define IS_DIR_SEPARATOR(c)    (IS_SLASH(c) || (c) == ':')

# if __DJGPP__

   /* DJGPP does not support neither hton[ls] nor ntoh[ls].  */
#  undef  htonl
#  define htonl(hostlong)  (hostlong)  /*  No op.  */
#  undef  ntohl
#  define ntohl(netlong)   (netlong)   /*  No op.  */

   /*
       A newly created data base file gets an initial hash table directory
       that has a size of 8 * sizeof (off_t).  This is only possible if
       STATBLKSIZE (file system block size) is a multiple of 512.
       Unfortunately DJGPP's fstat does not always return a st_blksize value
       that is a multiple of 512.
   */
#  undef  STATBLKSIZE
#  define STATBLKSIZE  512


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

#  include <libc/unconst.h>
#  define STRIP_FULL_PATH_AND_EXTENSION(file_name)   \
   (__gnuc_extension__                               \
     ({                                              \
        char *_dst, *_src;                           \
        _dst = _src = unconst((file_name), char *);  \
        while (*_src++)                              \
          ;                                          \
        while ((_src - _dst) && (*--_src != '.'))    \
          ;                                          \
        for (*_src = '\0'; (_src - _dst); _src--)    \
          if (IS_DIR_SEPARATOR(*_src))               \
            break;                                   \
        if (_src - _dst)                             \
          while ((*_dst++ = *++_src))                \
            ;                                        \
        (file_name);                                 \
     })                                              \
   )

#  define CANONICALIZE_PATH(path)                    \
   (__gnuc_extension__                               \
     ({                                              \
        if ((path))                                  \
        {                                            \
          char *_p = unconst((path), char *);        \
          for (; *_p; _p++)                          \
            if (*_p == '\\')                         \
              *_p = '/';                             \
        }                                            \
        (path);                                      \
     })                                              \
   )

   /*
       It is not possible to use st_ino to determinated
       if .pag file is an hard link to .dir file.
   */
#  include <stdbool.h>
#  define PAG_FILE_IS_HARD_LINK_TO_DIR_FILE(file_name)                                                             \
   (__gnuc_extension__                                                                                             \
     ({                                                                                                            \
        bool _is_link = false;                                                                                     \
        char *_dir_file, *_pag_file;                                                                               \
        size_t length = (size_t)strlen(file_name);                                                                 \
                                                                                                                   \
        _dir_file = unconst((file_name), char *);                                                                  \
        _pag_file = malloc(length + 1);                                                                            \
        strcpy(_pag_file, _dir_file);                                                                              \
        _pag_file[--length] = 'g';                                                                                 \
        _pag_file[--length] = 'a';                                                                                 \
        _pag_file[--length] = 'p';                                                                                 \
                                                                                                                   \
        if (!access(_dir_file, F_OK) && !access(_pag_file, F_OK))                                                  \
        {                                                                                                          \
          struct stat _sb_dir, _sb_pag;                                                                            \
          if (!stat(_dir_file, &_sb_dir) && !stat(_pag_file, &_sb_pag))                                            \
          {                                                                                                        \
            if ((_sb_dir.st_dev == _sb_pag.st_dev) && (_sb_dir.st_size == _sb_pag.st_size))                        \
            {                                                                                                      \
              int _fd_dir = open(_dir_file, O_RDONLY | O_BINARY);                                                  \
              int _fd_pag = open(_pag_file, O_RDONLY | O_BINARY);                                                  \
              if ((_fd_dir > -1) && (_fd_pag > -1))                                                                \
              {                                                                                                    \
                char _buffer_dir[512], _buffer_pag[512];                                                           \
                ssize_t _bytes_read_dir, _bytes_read_pag;                                                          \
                                                                                                                   \
                _bytes_read_dir = read(_fd_dir, _buffer_dir, 512);                                                 \
                _bytes_read_pag = read(_fd_pag, _buffer_pag, 512);                                                 \
                if ((_bytes_read_dir > -1) && (_bytes_read_pag > -1) && (_bytes_read_dir == _bytes_read_pag))      \
                {                                                                                                  \
                  ssize_t _i;                                                                                      \
                  for (_i = 0; _i < _bytes_read_dir && _buffer_dir[_i] == _buffer_pag[_i]; _i++)                   \
                    ;                                                                                              \
                  if (_i != _bytes_read_dir)                                                                       \
                    goto _finished;                                                                                \
                                                                                                                   \
                  off_t _pos_dir = lseek(_fd_dir, -512, SEEK_END);                                                 \
                  off_t _pos_pag = lseek(_fd_pag, -512, SEEK_END);                                                 \
                  if ((_pos_dir > -1) && (_pos_pag > -1) && (_pos_dir == _pos_pag))                                \
                  {                                                                                                \
                    _bytes_read_dir = read(_fd_dir, _buffer_dir, 512);                                             \
                    _bytes_read_pag = read(_fd_pag, _buffer_pag, 512);                                             \
                    if ((_bytes_read_dir > -1) && (_bytes_read_pag > -1) && (_bytes_read_dir == _bytes_read_pag))  \
                    {                                                                                              \
                      for (_i = 0; _i < _bytes_read_dir && _buffer_dir[_i] == _buffer_pag[_i]; _i++)               \
                        ;                                                                                          \
                      if (_i != _bytes_read_dir)                                                                   \
                        goto _finished;                                                                            \
                      _is_link = true;                                                                             \
                    }                                                                                              \
                  }                                                                                                \
                }                                                                                                  \
                                                                                                                   \
_finished:                                                                                                         \
                close(_fd_dir);                                                                                    \
                close(_fd_pag);                                                                                    \
              }                                                                                                    \
            }                                                                                                      \
          }                                                                                                        \
        }                                                                                                          \
        (_is_link);                                                                                                \
     })                                                                                                            \
   )

#  define HAS_LFN_SUPPORT(name)  (pathconf((name), _PC_NAME_MAX) > 12)
#  define FILE_EXISTS(name)      (access((name), R_OK) == 0)

#  define _GDBMTOOLRC                                                                                                     \
   (__gnuc_extension__                                                                                                    \
     ({                                                                                                                   \
        char *gdbmtoolrc = HAS_LFN_SUPPORT(".gdbmtoolrc") && FILE_EXISTS(".gdbmtoolrc") ? ".gdbmtoolrc" : "_gdbmtoolrc";  \
        gdbmtoolrc;                                                                                                       \
     })                                                                                                                   \
   )

# else /* !__DJGPP__  */
#  define STRIP_FULL_PATH_AND_EXTENSION(file_name)  (strrchr ((file_name), '/'))
#  define CANONICALIZE_PATH(path)                   (path)
#  define HAS_LFN_SUPPORT(name)                     (0)
#  define _GDBMTOOLRC                               "_gdbmtoolrc"
# endif /* !__DJGPP__  */
#else /* not O_BINARY */
# define O_BINARY
# define IS_DIR_SEPARATOR(c)                        ((c) == '/')
# define STRIP_FULL_PATH_AND_EXTENSION(file_name)   (strrchr ((file_name), '/'))
# define CANONICALIZE_PATH(path)                    (path)
# define HAS_LFN_SUPPORT(name)                      (1)
# define _GDBMTOOLRC                                ".gdbmtoolrc"
#endif /* not O_BINARY */
