/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1997-2010. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Purpose: Provides file and directory operations for Unix.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "erl_driver.h"
#include "erl_efile.h"
#include <utime.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_UIO_H
#include <sys/types.h>
#include <sys/uio.h>
#endif
#if defined(HAVE_SENDFILE) && (defined(__linux__) || (defined(__sun) && defined(__SVR4)))
#include <sys/sendfile.h>
#endif
#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
#define DARWIN 1
#endif
#ifdef DARWIN
#include <fcntl.h>
#endif /* DARWIN */
#ifdef VXWORKS
#include <ioLib.h>
#include <dosFsLib.h>
#include <nfsLib.h>
#include <sys/stat.h>
/*
** Not nice to include usrLib.h as MANY normal variable names get reported
** as shadowing globals, like 'i' for example.
** Instead we declare the only function we use here
*/
/*
* #include <usrLib.h>
*/
extern STATUS copy(char *, char *);
#include <errno.h>
#endif
#ifdef SUNOS4
# define getcwd(buf, size) getwd(buf)
#endif
/* Find a definition of MAXIOV, that is used in the code later. */
#if defined IOV_MAX
#define MAXIOV IOV_MAX
#elif defined UIO_MAXIOV
#define MAXIOV UIO_MAXIOV
#else
#define MAXIOV 16
#endif
/*
* Macros for testing file types.
*/
#define ISDIR(st) (((st).st_mode & S_IFMT) == S_IFDIR)
#define ISREG(st) (((st).st_mode & S_IFMT) == S_IFREG)
#define ISDEV(st) \
(((st).st_mode&S_IFMT) == S_IFCHR || ((st).st_mode&S_IFMT) == S_IFBLK)
#define ISLNK(st) (((st).st_mode & S_IFLNK) == S_IFLNK)
#ifdef NO_UMASK
#define FILE_MODE 0644
#define DIR_MODE 0755
#else
#define FILE_MODE 0666
#define DIR_MODE 0777
#endif
#ifdef VXWORKS /* Currently only used on vxworks */
#define EF_ALLOC(S) driver_alloc((S))
#define EF_REALLOC(P, S) driver_realloc((P), (S))
#define EF_SAFE_ALLOC(S) ef_safe_alloc((S))
#define EF_SAFE_REALLOC(P, S) ef_safe_realloc((P), (S))
#define EF_FREE(P) do { if((P)) driver_free((P)); } while(0)
void erl_exit(int n, char *fmt, ...);
static void *ef_safe_alloc(Uint s)
{
void *p = EF_ALLOC(s);
if (!p) erl_exit(1,
"unix efile drv: Can't allocate %d bytes of memory\n",
s);
return p;
}
#if 0 /* Currently not used */
static void *ef_safe_realloc(void *op, Uint s)
{
void *p = EF_REALLOC(op, s);
if (!p) erl_exit(1,
"unix efile drv: Can't reallocate %d bytes of memory\n",
s);
return p;
}
#endif /* #if 0 */
#endif /* #ifdef VXWORKS */
#define IS_DOT_OR_DOTDOT(s) \
(s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0')))
#ifdef VXWORKS
static int vxworks_to_posix(int vx_errno);
#endif
/*
** VxWorks (not) strikes again. Too long RESULTING paths
** may give the infamous bus error. Have to check ALL
** filenames and pathnames. No wonder the emulator is slow on
** these cards...
*/
#ifdef VXWORKS
#define CHECK_PATHLEN(Name, ErrInfo) \
if (path_size(Name) > PATH_MAX) { \
errno = ENAMETOOLONG; \
return check_error(-1, ErrInfo); \
}
#else
#define CHECK_PATHLEN(X,Y) /* Nothing */
#endif
static int check_error(int result, Efile_error* errInfo);
static int
check_error(int result, Efile_error *errInfo)
{
if (result < 0) {
#ifdef VXWORKS
errInfo->posix_errno = errInfo->os_errno = vxworks_to_posix(errno);
#else
errInfo->posix_errno = errInfo->os_errno = errno;
#endif
return 0;
}
return 1;
}
#ifdef VXWORKS
/*
* VxWorks has different error codes for different file systems.
* We map those to POSIX ones.
*/
static int
vxworks_to_posix(int vx_errno)
{
DEBUGF(("[vxworks_to_posix] vx_errno: %08x\n", vx_errno));
switch (vx_errno) {
/* dosFsLib mapping */
#ifdef S_dosFsLib_FILE_ALREADY_EXISTS
case S_dosFsLib_FILE_ALREADY_EXISTS: return EEXIST;
#else
case S_dosFsLib_FILE_EXISTS: return EEXIST;
#endif
#ifdef S_dosFsLib_BAD_DISK
case S_dosFsLib_BAD_DISK: return EIO;
#endif
#ifdef S_dosFsLib_CANT_CHANGE_ROOT
case S_dosFsLib_CANT_CHANGE_ROOT: return EINVAL;
#endif
#ifdef S_dosFsLib_NO_BLOCK_DEVICE
case S_dosFsLib_NO_BLOCK_DEVICE: return ENOTBLK;
#endif
#ifdef S_dosFsLib_BAD_SEEK
case S_dosFsLib_BAD_SEEK: return ESPIPE;
#endif
case S_dosFsLib_VOLUME_NOT_AVAILABLE: return ENXIO;
case S_dosFsLib_DISK_FULL: return ENOSPC;
case S_dosFsLib_FILE_NOT_FOUND: return ENOENT;
case S_dosFsLib_NO_FREE_FILE_DESCRIPTORS: return ENFILE;
case S_dosFsLib_INVALID_NUMBER_OF_BYTES: return EINVAL;
case S_dosFsLib_ILLEGAL_NAME: return EINVAL;
case S_dosFsLib_CANT_DEL_ROOT: return EACCES;
case S_dosFsLib_NOT_FILE: return EISDIR;
case S_dosFsLib_NOT_DIRECTORY: return ENOTDIR;
case S_dosFsLib_NOT_SAME_VOLUME: return EXDEV;
case S_dosFsLib_READ_ONLY: return EACCES;
case S_dosFsLib_ROOT_DIR_FULL: return ENOSPC;
case S_dosFsLib_DIR_NOT_EMPTY: return EEXIST;
case S_dosFsLib_NO_LABEL: return ENXIO;
case S_dosFsLib_INVALID_PARAMETER: return EINVAL;
case S_dosFsLib_NO_CONTIG_SPACE: return ENOSPC;
case S_dosFsLib_FD_OBSOLETE: return EBADF;
case S_dosFsLib_DELETED: return EINVAL;
case S_dosFsLib_INTERNAL_ERROR: return EIO;
case S_dosFsLib_WRITE_ONLY: return EACCES;
/* nfsLib mapping - is needed since Windriver has used */
/* inconsistent error codes (errno.h/nfsLib.h). */
case S_nfsLib_NFS_OK: return 0;
case S_nfsLib_NFSERR_PERM: return EPERM;
case S_nfsLib_NFSERR_NOENT: return ENOENT;
case S_nfsLib_NFSERR_IO: return EIO;
case S_nfsLib_NFSERR_NXIO: return ENXIO;
#ifdef S_nfsLib_NFSERR_ACCES
case S_nfsLib_NFSERR_ACCES: return EACCES;
#else
case S_nfsLib_NFSERR_ACCESS: return EACCES;
#endif
case S_nfsLib_NFSERR_EXIST: return EEXIST;
case S_nfsLib_NFSERR_NODEV: return ENODEV;
case S_nfsLib_NFSERR_NOTDIR: return ENOTDIR;
case S_nfsLib_NFSERR_ISDIR: return EISDIR;
case S_nfsLib_NFSERR_FBIG: return EFBIG;
case S_nfsLib_NFSERR_NOSPC: return ENOSPC;
case S_nfsLib_NFSERR_ROFS: return EROFS;
case S_nfsLib_NFSERR_NAMETOOLONG: return ENAMETOOLONG;
case S_nfsLib_NFSERR_NOTEMPTY: return EEXIST;
case S_nfsLib_NFSERR_DQUOT: return ENOSPC;
case S_nfsLib_NFSERR_STALE: return EINVAL;
case S_nfsLib_NFSERR_WFLUSH: return ENXIO;
/* And sometimes (...) the error codes are from ioLib (as in the */
/* case of the (for nfsLib) unimplemented rename function) */
case S_ioLib_DISK_NOT_PRESENT: return EIO;
#if S_ioLib_DISK_NOT_PRESENT != S_ioLib_NO_DRIVER
case S_ioLib_NO_DRIVER: return ENXIO;
#endif
case S_ioLib_UNKNOWN_REQUEST: return ENOSYS;
case S_ioLib_DEVICE_TIMEOUT: return EIO;
#ifdef S_ioLib_UNFORMATED
/* Added (VxWorks 5.2 -> 5.3.1) */
#if S_ioLib_UNFORMATED != S_ioLib_DEVICE_TIMEOUT
case S_ioLib_UNFORMATED: return EIO;
#endif
#endif
#if S_ioLib_DEVICE_TIMEOUT != S_ioLib_DEVICE_ERROR
case S_ioLib_DEVICE_ERROR: return ENXIO;
#endif
case S_ioLib_WRITE_PROTECTED: return EACCES;
case S_ioLib_NO_FILENAME: return EINVAL;
case S_ioLib_CANCELLED: return EINTR;
case S_ioLib_NO_DEVICE_NAME_IN_PATH: return EINVAL;
case S_ioLib_NAME_TOO_LONG: return ENAMETOOLONG;
#ifdef S_objLib_OBJ_UNAVAILABLE
case S_objLib_OBJ_UNAVAILABLE: return ENOENT;
#endif
/* Temporary workaround for a weird error in passFs
(VxWorks Simsparc only). File operation fails because of
ENOENT, but errno is not set. */
#ifdef SIMSPARCSOLARIS
case 0: return ENOENT;
#endif
}
/* If the error code matches none of the above, assume */
/* it is a POSIX one already. The upper bits (>=16) are */
/* cleared since VxWorks uses those bits to indicate in */
/* what module the error occured. */
return vx_errno & 0xffff;
}
static int
vxworks_enotsup(Efile_error *errInfo)
{
errInfo->posix_errno = errInfo->os_errno = ENOTSUP;
return 0;
}
static int
count_path_length(char *pathname, char *pathname2)
{
static int stack[PATH_MAX / 2 + 1];
int sp = 0;
char *tmp;
char *cpy = NULL;
int i;
int sum;
for(i = 0;i < 2;++i) {
if (!i) {
cpy = EF_SAFE_ALLOC(strlen(pathname)+1);
strcpy(cpy, pathname);
} else if (pathname2 != NULL) {
EF_FREE(cpy);
cpy = EF_SAFE_ALLOC(strlen(pathname2)+1);
strcpy(cpy, pathname2);
} else
break;
for (tmp = strtok(cpy,"/"); tmp != NULL; tmp = strtok(NULL,"/")) {
if (!strcmp(tmp,"..") && sp > 0)
--sp;
else if (strcmp(tmp,"."))
stack[sp++] = strlen(tmp);
}
}
if (cpy != NULL)
EF_FREE(cpy);
sum = 0;
for(i = 0;i < sp; ++i)
sum += stack[i]+1;
return (sum) ? sum : 1;
}
static int
path_size(char *pathname)
{
static char currdir[PATH_MAX+2];
if (*pathname == '/')
return count_path_length(pathname,NULL);
ioDefPathGet(currdir);
strcat(currdir,"/");
return count_path_length(currdir,pathname);
}
#endif /* VXWORKS */
int
efile_mkdir(Efile_error* errInfo, /* Where to return error codes. */
char* name) /* Name of directory to create. */
{
CHECK_PATHLEN(name,errInfo);
#ifdef NO_MKDIR_MODE
#ifdef VXWORKS
/* This is a VxWorks/nfs workaround for erl_tar to create
* non-existant directories. (of some reason (...) VxWorks
* returns, the *non-module-prefixed*, 0xd code when
* trying to create a directory in a directory that doesn't exist).
* (see efile_openfile)
*/
if (mkdir(name) < 0) {
struct stat sb;
if (name[0] == '\0') {
/* Return the correct error code enoent */
errno = S_nfsLib_NFSERR_NOENT;
} else if (stat(name, &sb) == OK) {
errno = S_nfsLib_NFSERR_EXIST;
} else if((strchr(name, '/') != NULL) && (errno == 0xd)) {
/* Return the correct error code enoent */
errno = S_nfsLib_NFSERR_NOENT;
}
return check_error(-1, errInfo);
} else return 1;
#else
return check_error(mkdir(name), errInfo);
#endif
#else
return check_error(mkdir(name, DIR_MODE), errInfo);
#endif
}
int
efile_rmdir(Efile_error* errInfo, /* Where to return error codes. */
char* name) /* Name of directory to delete. */
{
CHECK_PATHLEN(name, errInfo);
if (rmdir(name) == 0) {
return 1;
}
#ifdef VXWORKS
if (name[0] == '\0') {
/* Return the correct error code enoent */
errno = S_nfsLib_NFSERR_NOENT;
}
#else
if (errno == ENOTEMPTY) {
errno = EEXIST;
}
if (errno == EEXIST) {
int saved_errno = errno;
struct stat file_stat;
struct stat cwd_stat;
/*
* The error code might be wrong if this is the current directory.
*/
if (stat(name, &file_stat) == 0 && stat(".", &cwd_stat) == 0 &&
file_stat.st_ino == cwd_stat.st_ino &&
file_stat.st_dev == cwd_stat.st_dev) {
saved_errno = EINVAL;
}
errno = saved_errno;
}
#endif
return check_error(-1, errInfo);
}
int
efile_delete_file(Efile_error* errInfo, /* Where to return error codes. */
char* name) /* Name of file to delete. */
{
CHECK_PATHLEN(name,errInfo);
if (unlink(name) == 0) {
return 1;
}
if (errno == EISDIR) { /* Linux sets the wrong error code. */
errno = EPERM;
}
return check_error(-1, errInfo);
}
/*
*---------------------------------------------------------------------------
*
* Changes the name of an existing file or directory, from src to dst.
* If src and dst refer to the same file or directory, does nothing
* and returns success. Otherwise if dst already exists, it will be
* deleted and replaced by src subject to the following conditions:
* If src is a directory, dst may be an empty directory.
* If src is a file, dst may be a file.
* In any other situation where dst already exists, the rename will
* fail.
*
* Results:
* If the directory was successfully created, returns 1.
* Otherwise the return value is 0 and errno is set to
* indicate the error. Some possible values for errno are:
*
* EACCES: src or dst parent directory can't be read and/or written.
* EEXIST: dst is a non-empty directory.
* EINVAL: src is a root directory or dst is a subdirectory of src.
* EISDIR: dst is a directory, but src is not.
* ENOENT: src doesn't exist, or src or dst is "".
* ENOTDIR: src is a directory, but dst is not.
* EXDEV: src and dst are on different filesystems.
*
* Side effects:
* The implementation of rename may allow cross-filesystem renames,
* but the caller should be prepared to emulate it with copy and
* delete if errno is EXDEV.
*
*---------------------------------------------------------------------------
*/
int
efile_rename(Efile_error* errInfo, /* Where to return error codes. */
char* src, /* Original name. */
char* dst) /* New name. */
{
CHECK_PATHLEN(src,errInfo);
CHECK_PATHLEN(dst,errInfo);
#ifdef VXWORKS
/* First check if src == dst, if so, just return. */
/* VxWorks dos file system destroys the file otherwise, */
/* VxWorks nfs file system rename doesn't work at all. */
if(strcmp(src, dst) == 0)
return 1;
#endif
if (rename(src, dst) == 0) {
return 1;
}
#ifdef VXWORKS
/* nfs for VxWorks doesn't support rename. We try to emulate it */
/* (by first copying src to dst and then deleting src). */
if(errno == S_ioLib_UNKNOWN_REQUEST && /* error code returned
by ioLib (!) */
copy(src, dst) == OK &&
unlink(src) == OK)
return 1;
#endif
if (errno == ENOTEMPTY) {
errno = EEXIST;
}
#if defined (sparc) && !defined(VXWORKS)
/*
* SunOS 4.1.4 reports overwriting a non-empty directory with a
* directory as EINVAL instead of EEXIST (first rule out the correct
* EINVAL result code for moving a directory into itself). Must be
* conditionally compiled because realpath() is only defined on SunOS.
*/
if (errno == EINVAL) {
char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
DIR *dirPtr;
struct dirent *dirEntPtr;
#ifdef PURIFY
memset(srcPath, '\0', sizeof(srcPath));
memset(dstPath, '\0', sizeof(dstPath));
#endif
if ((realpath(src, srcPath) != NULL)
&& (realpath(dst, dstPath) != NULL)
&& (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) {
dirPtr = opendir(dst);
if (dirPtr != NULL) {
while ((dirEntPtr = readdir(dirPtr)) != NULL) {
if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
(strcmp(dirEntPtr->d_name, "..") != 0)) {
errno = EEXIST;
closedir(dirPtr);
return check_error(-1, errInfo);
}
}
closedir(dirPtr);
}
}
errno = EINVAL;
}
#endif /* sparc */
if (strcmp(src, "/") == 0) {
/*
* Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
* instead of EINVAL.
*/
errno = EINVAL;
}
/*
* DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a
* file across filesystems and the parent directory of that file is
* not writable. Most other systems return EXDEV. Does nothing to
* correct this behavior.
*/
return check_error(-1, errInfo);
}
int
efile_chdir(Efile_error* errInfo, /* Where to return error codes. */
char* name) /* Name of directory to make current. */
{
CHECK_PATHLEN(name, errInfo);
return check_error(chdir(name), errInfo);
}
int
efile_getdcwd(Efile_error* errInfo, /* Where to return error codes. */
int drive, /* 0 - current, 1 - A, 2 - B etc. */
char* buffer, /* Where to return the current
directory. */
size_t size) /* Size of buffer. */
{
if (drive == 0) {
if (getcwd(buffer, size) == NULL)
return check_error(-1, errInfo);
#ifdef SIMSPARCSOLARIS
/* We get "host:" prepended to the dirname - remove!. */
{
int i = 0;
int j = 0;
while ((buffer[i] != ':') && (buffer[i] != '\0')) i++;
if (buffer[i] == ':') {
i++;
while ((buffer[j++] = buffer[i++]) != '\0');
}
}
#endif
return 1;
}
/*
* Drives other than 0 is not supported on Unix.
*/
errno = ENOTSUP;
return check_error(-1, errInfo);
}
int
efile_readdir(Efile_error* errInfo, /* Where to return error codes. */
char* name, /* Name of directory to open. */
EFILE_DIR_HANDLE* p_dir_handle, /* Pointer to directory
handle of
open directory.*/
char* buffer, /* Pointer to buffer for
one filename. */
size_t *size) /* in-out Size of buffer, length
of name. */
{
DIR *dp; /* Pointer to directory structure. */
struct dirent* dirp; /* Pointer to directory entry. */
/*
* If this is the first call, we must open the directory.
*/
CHECK_PATHLEN(name, errInfo);
if (*p_dir_handle == NULL) {
dp = opendir(name);
if (dp == NULL)
return check_error(-1, errInfo);
*p_dir_handle = (EFILE_DIR_HANDLE) dp;
}
/*
* Retrieve the name of the next file using the directory handle.
*/
dp = *((DIR **)((void *)p_dir_handle));
for (;;) {
dirp = readdir(dp);
if (dirp == NULL) {
closedir(dp);
return 0;
}
if (IS_DOT_OR_DOTDOT(dirp->d_name))
continue;
buffer[0] = '\0';
strncat(buffer, dirp->d_name, (*size)-1);
*size = strlen(dirp->d_name);
return 1;
}
}
int
efile_openfile(Efile_error* errInfo, /* Where to return error codes. */
char* name, /* Name of directory to open. */
int flags, /* Flags to user for opening. */
int* pfd, /* Where to store the file
descriptor. */
Sint64 *pSize) /* Where to store the size of the
file. */
{
struct stat statbuf;
int fd;
int mode; /* Open mode. */
#ifdef VXWORKS
char pathbuff[PATH_MAX+2];
char sbuff[PATH_MAX*2];
char *totbuff = sbuff;
int nameneed;
#endif
CHECK_PATHLEN(name, errInfo);
#ifdef VXWORKS
/* Have to check that it's not a directory. */
if (stat(name,&statbuf) != ERROR && ISDIR(statbuf)) {
errno = EISDIR;
return check_error(-1, errInfo);
}
#endif
if (stat(name, &statbuf) >= 0 && !ISREG(statbuf)) {
#if !defined(VXWORKS) && !defined(OSE)
/*
* For UNIX only, here is some ugly code to allow
* /dev/null to be opened as a file.
*
* Assumption: The i-node number for /dev/null cannot be zero.
*/
static ino_t dev_null_ino = 0;
if (dev_null_ino == 0) {
struct stat nullstatbuf;
if (stat("/dev/null", &nullstatbuf) >= 0) {
dev_null_ino = nullstatbuf.st_ino;
}
}
if (!(dev_null_ino && statbuf.st_ino == dev_null_ino)) {
#endif
errno = EISDIR;
return check_error(-1, errInfo);
#if !defined(VXWORKS) && !defined(OSE)
}
#endif
}
switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) {
case EFILE_MODE_READ:
mode = O_RDONLY;
break;
case EFILE_MODE_WRITE:
if (flags & EFILE_NO_TRUNCATE)
mode = O_WRONLY | O_CREAT;
else
mode = O_WRONLY | O_CREAT | O_TRUNC;
break;
case EFILE_MODE_READ_WRITE:
mode = O_RDWR | O_CREAT;
break;
default:
errno = EINVAL;
return check_error(-1, errInfo);
}
if (flags & EFILE_MODE_APPEND) {
mode &= ~O_TRUNC;
#ifndef VXWORKS
mode |= O_APPEND; /* Dont make VxWorks think things it shouldn't */
#endif
}
if (flags & EFILE_MODE_EXCL) {
mode |= O_EXCL;
}
#ifdef VXWORKS
if (*name != '/') {
/* Make sure it is an absolute pathname, because ftruncate needs it */
ioDefPathGet(pathbuff);
strcat(pathbuff,"/");
nameneed = strlen(pathbuff) + strlen(name) + 1;
if (nameneed > PATH_MAX*2)
totbuff = EF_SAFE_ALLOC(nameneed);
strcpy(totbuff,pathbuff);
strcat(totbuff,name);
fd = open(totbuff, mode, FILE_MODE);
if (totbuff != sbuff)
EF_FREE(totbuff);
} else {
fd = open(name, mode, FILE_MODE);
}
#else
fd = open(name, mode, FILE_MODE);
#endif
#ifdef VXWORKS
/* This is a VxWorks/nfs workaround for erl_tar to create
* non-existant directories. (of some reason (...) VxWorks
* returns, the *non-module-prefixed*, 0xd code when
* trying to write a file in a directory that doesn't exist).
* (see efile_mkdir)
*/
if ((fd < 0) && (strchr(name, '/') != NULL) && (errno == 0xd)) {
/* Return the correct error code enoent */
errno = S_nfsLib_NFSERR_NOENT;
return check_error(-1, errInfo);
}
#endif
if (!check_error(fd, errInfo))
return 0;
*pfd = fd;
if (pSize) {
*pSize = statbuf.st_size;
}
return 1;
}
int
efile_may_openfile(Efile_error* errInfo, char *name) {
struct stat statbuf; /* Information about the file */
int result;
result = stat(name, &statbuf);
if (!check_error(result, errInfo))
return 0;
if (!ISREG(statbuf)) {
errno = EISDIR;
return check_error(-1, errInfo);
}
return 1;
}
void
efile_closefile(int fd)
{
close(fd);
}
int
efile_fdatasync(Efile_error *errInfo, /* Where to return error codes. */
int fd) /* File descriptor for file to sync data. */
{
#ifdef HAVE_FDATASYNC
return check_error(fdatasync(fd), errInfo);
#else
return efile_fsync(errInfo, fd);
#endif
}
int
efile_fsync(Efile_error *errInfo, /* Where to return error codes. */
int fd) /* File descriptor for file to sync. */
{
#ifdef NO_FSYNC
#ifdef VXWORKS
return check_error(ioctl(fd, FIOSYNC, 0), errInfo);
#else
undefined fsync
#endif /* VXWORKS */
#else
#if defined(DARWIN) && defined(F_FULLFSYNC)
return check_error(fcntl(fd, F_FULLFSYNC), errInfo);
#else
return check_error(fsync(fd), errInfo);
#endif /* DARWIN */
#endif /* NO_FSYNC */
}
int
efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo,
char* name, int info_for_link)
{
struct stat statbuf; /* Information about the file */
struct tm *timep; /* Broken-apart filetime. */
int result;
#ifdef VXWORKS
if (*name == '\0') {
errInfo->posix_errno = errInfo->os_errno = ENOENT;
return 0;
}
#endif
CHECK_PATHLEN(name, errInfo);
if (info_for_link) {
#if (defined(VXWORKS))
result = stat(name, &statbuf);
#else
result = lstat(name, &statbuf);
#endif
} else {
result = stat(name, &statbuf);
}
if (!check_error(result, errInfo)) {
return 0;
}
#if SIZEOF_OFF_T == 4
pInfo->size_high = 0;
#else
pInfo->size_high = (Uint32)(statbuf.st_size >> 32);
#endif
pInfo->size_low = (Uint32)statbuf.st_size;
#ifdef NO_ACCESS
/* Just look at read/write access for owner. */
#ifdef VXWORKS
pInfo->access = FA_NONE;
if(statbuf.st_mode & S_IRUSR)
pInfo->access |= FA_READ;
if(statbuf.st_mode & S_IWUSR)
pInfo->access |= FA_WRITE;
#else
pInfo->access = ((statbuf.st_mode >> 6) & 07) >> 1;
#endif /* VXWORKS */
#else
pInfo->access = FA_NONE;
if (access(name, R_OK) == 0)
pInfo->access |= FA_READ;
if (access(name, W_OK) == 0)
pInfo->access |= FA_WRITE;
#endif
if (ISDEV(statbuf))
pInfo->type = FT_DEVICE;
else if (ISDIR(statbuf))
pInfo->type = FT_DIRECTORY;
else if (ISREG(statbuf))
pInfo->type = FT_REGULAR;
else if (ISLNK(statbuf))
pInfo->type = FT_SYMLINK;
else
pInfo->type = FT_OTHER;
#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS)
{
/* Use the reentrant version of localtime() */
static struct tm local_tm;
#define localtime(a) (localtime_r((a), &local_tm), &local_tm)
#endif
#define GET_TIME(dst, src) \
timep = localtime(&statbuf.src); \
(dst).year = timep->tm_year+1900; \
(dst).month = timep->tm_mon+1; \
(dst).day = timep->tm_mday; \
(dst).hour = timep->tm_hour; \
(dst).minute = timep->tm_min; \
(dst).second = timep->tm_sec
GET_TIME(pInfo->accessTime, st_atime);
GET_TIME(pInfo->modifyTime, st_mtime);
GET_TIME(pInfo->cTime, st_ctime);
#undef GET_TIME
#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS)
}
#endif
pInfo->mode = statbuf.st_mode;
pInfo->links = statbuf.st_nlink;
pInfo->major_device = statbuf.st_dev;
pInfo->minor_device = statbuf.st_rdev;
pInfo->inode = statbuf.st_ino;
pInfo->uid = statbuf.st_uid;
pInfo->gid = statbuf.st_gid;
return 1;
}
int
efile_write_info(Efile_error *errInfo, Efile_info *pInfo, char *name)
{
CHECK_PATHLEN(name, errInfo);
#ifdef VXWORKS
if (pInfo->mode != -1) {
int fd;
struct stat statbuf;
fd = open(name, O_RDONLY, 0);
if (!check_error(fd, errInfo))
return 0;
if (fstat(fd, &statbuf) < 0) {
close(fd);
return check_error(-1, errInfo);
}
if (pInfo->mode & S_IWUSR) {
/* clear read only bit */
statbuf.st_attrib &= ~DOS_ATTR_RDONLY;
} else {
/* set read only bit */
statbuf.st_attrib |= DOS_ATTR_RDONLY;
}
/* This should work for dos files but not for nfs ditos, so don't
* report errors (to avoid problems when running e.g. erl_tar)
*/
ioctl(fd, FIOATTRIBSET, statbuf.st_attrib);
close(fd);
}
#else
/*
* On some systems chown will always fail for a non-root user unless
* POSIX_CHOWN_RESTRICTED is not set. Others will succeed as long as
* you don't try to chown a file to someone besides youself.
*/
if (chown(name, pInfo->uid, pInfo->gid) && errno != EPERM) {
return check_error(-1, errInfo);
}
if (pInfo->mode != -1) {
mode_t newMode = pInfo->mode & (S_ISUID | S_ISGID |
S_IRWXU | S_IRWXG | S_IRWXO);
if (chmod(name, newMode)) {
newMode &= ~(S_ISUID | S_ISGID);
if (chmod(name, newMode)) {
return check_error(-1, errInfo);
}
}
}
#endif /* !VXWORKS */
if (pInfo->accessTime.year != -1 && pInfo->modifyTime.year != -1) {
struct utimbuf tval;
struct tm timebuf;
#define MKTIME(tb, ts) \
timebuf.tm_year = ts.year-1900; \
timebuf.tm_mon = ts.month-1; \
timebuf.tm_mday = ts.day; \
timebuf.tm_hour = ts.hour; \
timebuf.tm_min = ts.minute; \
timebuf.tm_sec = ts.second; \
timebuf.tm_isdst = -1; \
if ((tb = mktime(&timebuf)) == (time_t) -1) { \
errno = EINVAL; \
return check_error(-1, errInfo); \
}
MKTIME(tval.actime, pInfo->accessTime);
MKTIME(tval.modtime, pInfo->modifyTime);
#undef MKTIME
#ifdef VXWORKS
/* VxWorks' utime doesn't work when the file is a nfs mounted
* one, don't report error if utime fails.
*/
utime(name, &tval);
return 1;
#else
return check_error(utime(name, &tval), errInfo);
#endif
}
return 1;
}
int
efile_write(Efile_error* errInfo, /* Where to return error codes. */
int flags, /* Flags given when file was
opened. */
int fd, /* File descriptor to write to. */
char* buf, /* Buffer to write. */
size_t count) /* Number of bytes to write. */
{
ssize_t written; /* Bytes written in last operation. */
#ifdef VXWORKS
if (flags & EFILE_MODE_APPEND) {
lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */
}
#endif
while (count > 0) {
if ((written = write(fd, buf, count)) < 0) {
if (errno != EINTR)
return check_error(-1, errInfo);
else
written = 0;
}
ASSERT(written <= count);
buf += written;
count -= written;
}
return 1;
}
int
efile_writev(Efile_error* errInfo, /* Where to return error codes */
int flags, /* Flags given when file was
* opened */
int fd, /* File descriptor to write to */
SysIOVec* iov, /* Vector of buffer structs.
* The structs are unchanged
* after the call */
int iovcnt, /* Number of structs in vector */
size_t size) /* Number of bytes to write */
{
int cnt = 0; /* Buffers so far written */
int p = 0; /* Position in next buffer */
ASSERT(iovcnt >= 0);
#ifdef VXWORKS
if (flags & EFILE_MODE_APPEND) {
lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */
}
#endif
while (cnt < iovcnt) {
#ifdef HAVE_WRITEV
int w; /* Bytes written in this call */
int b = iovcnt - cnt; /* Buffers to write */
if (b > MAXIOV)
b = MAXIOV;
if (iov[cnt].iov_base && iov[cnt].iov_len > 0) {
if (b == 1) {
/* Degenerated io vector */
do {
w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p);
} while (w < 0 && errno == EINTR);
} else {
/* Non-empty vector first.
* Adjust pos in first buffer in case of
* previous incomplete writev */
iov[cnt].iov_base += p;
iov[cnt].iov_len -= p;
do {
w = writev(fd, &iov[cnt], b);
} while (w < 0 && errno == EINTR);
iov[cnt].iov_base -= p;
iov[cnt].iov_len += p;
}
if (w < 0)
return check_error(-1, errInfo);
} else {
/* Empty vector first - skip */
cnt++;
continue;
}
ASSERT(w >= 0);
/* Move forward to next vector to write */
for (; cnt < iovcnt; cnt++) {
if (iov[cnt].iov_base && iov[cnt].iov_len > 0) {
if (w < iov[cnt].iov_len)
break;
else
w -= iov[cnt].iov_len;
}
}
ASSERT(w >= 0);
p = w > 0 ? w : 0; /* Skip p bytes next writev */
#else /* #ifdef HAVE_WRITEV */
if (iov[cnt].iov_base && iov[cnt].iov_len > 0) {
/* Non-empty vector */
int w; /* Bytes written in this call */
while (p < iov[cnt].iov_len) {
do {
w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p);
} while (w < 0 && errno == EINTR);
if (w < 0)
return check_error(-1, errInfo);
p += w;
}
}
cnt++;
p = 0;
#endif /* #ifdef HAVE_WRITEV */
} /* while (cnt< iovcnt) */
size = 0; /* Avoid compiler warning */
return 1;
}
int
efile_read(Efile_error* errInfo, /* Where to return error codes. */
int flags, /* Flags given when file was opened. */
int fd, /* File descriptor to read from. */
char* buf, /* Buffer to read into. */
size_t count, /* Number of bytes to read. */
size_t *pBytesRead) /* Where to return number of
bytes read. */
{
ssize_t n;
for (;;) {
if ((n = read(fd, buf, count)) >= 0)
break;
else if (errno != EINTR)
return check_error(-1, errInfo);
}
*pBytesRead = (size_t) n;
return 1;
}
/* pread() and pwrite() */
/* Some unix systems, notably Solaris has these syscalls */
/* It is especially nice for i.e. the dets module to have support */
/* for this, even if the underlying OS dosn't support it, it is */
/* reasonably easy to work around by first calling seek, and then */
/* calling read(). */
/* This later strategy however changes the file pointer, which pread() */
/* does not do. We choose to ignore this and say that the location */
/* of the file pointer is undefined after a call to any of the p functions*/
int
efile_pread(Efile_error* errInfo, /* Where to return error codes. */
int fd, /* File descriptor to read from. */
Sint64 offset, /* Offset in bytes from BOF. */
char* buf, /* Buffer to read into. */
size_t count, /* Number of bytes to read. */
size_t *pBytesRead) /* Where to return
number of bytes read. */
{
#if defined(HAVE_PREAD) && defined(HAVE_PWRITE)
ssize_t n;
off_t off = (off_t) offset;
if (off != offset) {
errno = EINVAL;
return check_error(-1, errInfo);
}
for (;;) {
if ((n = pread(fd, buf, count, offset)) >= 0)
break;
else if (errno != EINTR)
return check_error(-1, errInfo);
}
*pBytesRead = (size_t) n;
return 1;
#else
{
int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL);
if (res) {
return efile_read(errInfo, 0, fd, buf, count, pBytesRead);
} else {
return res;
}
}
#endif
}
int
efile_pwrite(Efile_error* errInfo, /* Where to return error codes. */
int fd, /* File descriptor to write to. */
char* buf, /* Buffer to write. */
size_t count, /* Number of bytes to write. */
Sint64 offset) /* where to write it */
{
#if defined(HAVE_PREAD) && defined(HAVE_PWRITE)
ssize_t written; /* Bytes written in last operation. */
off_t off = (off_t) offset;
if (off != offset) {
errno = EINVAL;
return check_error(-1, errInfo);
}
while (count > 0) {
if ((written = pwrite(fd, buf, count, offset)) < 0) {
if (errno != EINTR)
return check_error(-1, errInfo);
else
written = 0;
}
ASSERT(written <= count);
buf += written;
count -= written;
offset += written;
}
return 1;
#else /* For unix systems that don't support pread() and pwrite() */
{
int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL);
if (res) {
return efile_write(errInfo, 0, fd, buf, count);
} else {
return res;
}
}
#endif
}
int
efile_seek(Efile_error* errInfo, /* Where to return error codes. */
int fd, /* File descriptor to do the seek on. */
Sint64 offset, /* Offset in bytes from the given
origin. */
int origin, /* Origin of seek (SEEK_SET, SEEK_CUR,
SEEK_END). */
Sint64 *new_location) /* Resulting new location in file. */
{
off_t off, result;
switch (origin) {
case EFILE_SEEK_SET: origin = SEEK_SET; break;
case EFILE_SEEK_CUR: origin = SEEK_CUR; break;
case EFILE_SEEK_END: origin = SEEK_END; break;
default:
errno = EINVAL;
return check_error(-1, errInfo);
}
off = (off_t) offset;
if (off != offset) {
errno = EINVAL;
return check_error(-1, errInfo);
}
errno = 0;
result = lseek(fd, off, origin);
/*
* Note that the man page for lseek (on SunOs 5) says:
*
* "if fildes is a remote file descriptor and offset is
* negative, lseek() returns the file pointer even if it is
* negative."
*/
if (result < 0 && errno == 0)
errno = EINVAL;
if (result < 0)
return check_error(-1, errInfo);
if (new_location) {
*new_location = result;
}
return 1;
}
int
efile_truncate_file(Efile_error* errInfo, int *fd, int flags)
{
#ifdef VXWORKS
off_t offset;
char namebuf[PATH_MAX+1];
char namebuf2[PATH_MAX+10];
int new;
int dummy;
int i;
int left;
static char buff[1024];
struct stat st;
Efile_error tmperr;
if ((offset = lseek(*fd, 0, 1)) < 0) {
return check_error((int) offset,errInfo);
}
if (ftruncate(*fd, offset) < 0) {
if (vxworks_to_posix(errno) != EINVAL) {
return check_error(-1, errInfo);
}
/*
** Kludge
*/
if(ioctl(*fd,FIOGETNAME,(int) namebuf) < 0) {
return check_error(-1, errInfo);
}
for(i=0;i<1000;++i) {
sprintf(namebuf2,"%s%d",namebuf,i);
CHECK_PATHLEN(namebuf2,errInfo);
if (stat(namebuf2,&st) < 0) {
break;
}
}
if (i > 1000) {
errno = EINVAL;
return check_error(-1, errInfo);
}
if (close(*fd) < 0) {
return check_error(-1, errInfo);
}
if (efile_rename(&tmperr,namebuf,namebuf2) < 0) {
i = check_error(-1,&tmperr);
if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,
fd,&dummy)) {
*fd = -1;
} else {
*errInfo = tmperr;
}
return i;
}
if ((*fd = open(namebuf2, O_RDONLY, 0)) < 0) {
i = check_error(-1,errInfo);
efile_rename(&tmperr,namebuf2,namebuf); /* at least try */
if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,
fd,&dummy)) {
*fd = -1;
} else {
lseek(*fd,offset,SEEK_SET);
}
return i;
}
/* Point of no return... */
if ((new = open(namebuf,O_RDWR | O_CREAT, FILE_MODE)) < 0) {
close(*fd);
*fd = -1;
return 0;
}
left = offset;
while (left) {
if ((i = read(*fd,buff,(left > 1024) ? 1024 : left)) < 0) {
i = check_error(-1,errInfo);
close(new);
close(*fd);
unlink(namebuf);
efile_rename(&tmperr,namebuf2,namebuf); /* at least try */
if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,
fd,&dummy)) {
*fd = -1;
} else {
lseek(*fd,offset,SEEK_SET);
}
return i;
}
left -= i;
if (write(new,buff,i) < 0) {
i = check_error(-1,errInfo);
close(new);
close(*fd);
unlink(namebuf);
rename(namebuf2,namebuf); /* at least try */
if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,
fd,&dummy)) {
*fd = -1;
} else {
lseek(*fd,offset,SEEK_SET);
}
return i;
}
}
close(*fd);
unlink(namebuf2);
close(new);
i = efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,fd,
&dummy);
if (i) {
lseek(*fd,offset,SEEK_SET);
}
return i;
}
return 1;
#else
#ifndef NO_FTRUNCATE
off_t offset;
return check_error((offset = lseek(*fd, 0, 1)) >= 0 &&
ftruncate(*fd, offset) == 0 ? 1 : -1,
errInfo);
#else
return 1;
#endif
#endif
}
int
efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size)
{
#ifdef VXWORKS
return vxworks_enotsup(errInfo);
#else
int len;
ASSERT(size > 0);
len = readlink(name, buffer, size-1);
if (len == -1) {
return check_error(-1, errInfo);
}
buffer[len] = '\0';
return 1;
#endif
}
int
efile_altname(Efile_error* errInfo, char* name, char* buffer, size_t size)
{
errno = ENOTSUP;
return check_error(-1, errInfo);
}
int
efile_link(Efile_error* errInfo, char* old, char* new)
{
#ifdef VXWORKS
return vxworks_enotsup(errInfo);
#else
return check_error(link(old, new), errInfo);
#endif
}
int
efile_symlink(Efile_error* errInfo, char* old, char* new)
{
#ifdef VXWORKS
return vxworks_enotsup(errInfo);
#else
return check_error(symlink(old, new), errInfo);
#endif
}
int
efile_fadvise(Efile_error* errInfo, int fd, Sint64 offset,
Sint64 length, int advise)
{
#ifdef HAVE_POSIX_FADVISE
return check_error(posix_fadvise(fd, offset, length, advise), errInfo);
#else
return check_error(0, errInfo);
#endif
}
#ifdef HAVE_SENDFILE
// For some reason the maximum size_t cannot be used as the max size
// 3GB seems to work on all platforms
#define SENDFILE_CHUNK_SIZE ((1 << 30) -1)
/*
* sendfile: The implementation of the sendfile system call varies
* a lot on different *nix platforms so to make the api similar in all
* we have to emulate some things in linux and play with variables on
* bsd/darwin.
*
* All of the calls will split a command which tries to send more than
* SENDFILE_CHUNK_SIZE of data at once.
*
* On platforms where *nbytes of 0 does not mean the entire file, this is
* simulated.
*
* It could be possible to implement header/trailer in sendfile. Though
* you would have to emulate it in linux and on BSD/Darwin some complex
* calculations have to be made when using a non blocking socket to figure
* out how much of the header/file/trailer was sent in each command.
*/
int
efile_sendfile(Efile_error* errInfo, int in_fd, int out_fd,
off_t *offset, Uint64 *nbytes, struct t_sendfile_hdtl* hdtl)
{
Uint64 written = 0;
#if defined(__linux__)
ssize_t retval;
do {
// check if *nbytes is 0 or greater than chunk size
if (*nbytes == 0 || *nbytes > SENDFILE_CHUNK_SIZE)
retval = sendfile(out_fd, in_fd, offset, SENDFILE_CHUNK_SIZE);
else
retval = sendfile(out_fd, in_fd, offset, *nbytes);
if (retval > 0) {
written += retval;
*nbytes -= retval;
}
} while (retval != -1 && retval == SENDFILE_CHUNK_SIZE);
*nbytes = written;
return check_error(retval == -1 ? -1 : 0, errInfo);
#elif defined(__sun) && defined(__SVR4) && defined(HAVE_SENDFILEV)
ssize_t retval;
size_t len;
sendfilevec_t fdrec;
fdrec.sfv_fd = in_fd;
fdrec.sfv_flag = SFV_NOWAIT;
do {
fdrec.sfv_off = *offset;
len = 0;
// check if *nbytes is 0 or greater than chunk size
if (*nbytes == 0 || *nbytes > SENDFILE_CHUNK_SIZE)
fdrec.sfv_len = SENDFILE_CHUNK_SIZE;
else
fdrec.sfv_len = *nbytes;
retval = sendfilev(out_fd, &fdrec, 1, &len);
if (retval != -1 || errno == EAGAIN || errno == EINTR) {
*offset += len;
*nbytes -= len;
written += len;
}
} while (len == SENDFILE_CHUNK_SIZE);
*nbytes = written;
return check_error(retval == -1 ? -1 : 0, errInfo);
#elif defined(DARWIN)
int retval;
off_t len;
do {
// check if *nbytes is 0 or greater than chunk size
if(*nbytes > SENDFILE_CHUNK_SIZE)
len = SENDFILE_CHUNK_SIZE;
else
len = *nbytes;
retval = sendfile(in_fd, out_fd, *offset, &len, NULL, 0);
if (retval != -1 || errno == EAGAIN || errno == EINTR) {
*offset += len;
*nbytes -= len;
written += len;
}
} while (len == SENDFILE_CHUNK_SIZE);
*nbytes = written;
return check_error(retval, errInfo);
#elif defined(__FreeBSD__) || defined(__DragonFly__)
off_t len;
int retval;
do {
if (*nbytes > SENDFILE_CHUNK_SIZE)
retval = sendfile(in_fd, out_fd, *offset, SENDFILE_CHUNK_SIZE,
NULL, &len, 0);
else
retval = sendfile(in_fd, out_fd, *offset, *nbytes, NULL, &len, 0);
if (retval != -1 || errno == EAGAIN || errno == EINTR) {
*offset += len;
*nbytes -= len;
written += len;
}
} while(len == SENDFILE_CHUNK_SIZE);
*nbytes = written;
return check_error(retval, errInfo);
#endif
}
#endif /* HAVE_SENDFILE */