📄 file.c
字号:
#elif defined( __BEOS__ ) || defined( __ECOS__ ) || defined( __RTEMS__ ) || \
defined( __SYMBIAN32__ ) || defined( __TANDEM_NSK__ ) || \
defined( __TANDEM_OSS__ ) || defined( __UNIX__ )
/* Tandem doesn't have ftruncate() even though there's a manpage for it
(which claims it's prototyped in sys/types.h (!!)). unistd.h has it
protected by ( _XOPEN_SOURCE_EXTENDED == 1 && _TNS_R_TARGET ), which
implies that we'd better emulate it if we want to make use of it. For
now we do nothing, this is just a placeholder if the Guardian native
file layer isn't available */
#if defined( __TANDEM_NSK__ ) || defined( __TANDEM_OSS__ )
int ftruncate( int fd, off_t length )
{
return( 0 );
}
#endif /* Tandem */
/* Open/close a file stream */
#ifdef DDNAME_IO
/* DDNAME I/O can be used under MVS. Low-level POSIX I/O APIs can't be
used at this level, only stream I/O functions can be used. For
sFileOpen:
- File permissions are controlled by RACF (or SAF compatable product)
and should not be set by the program.
- No locking mechanism is implemented */
int sFileOpen( STREAM *stream, const char *fileName, const int mode )
{
#pragma convlit( suspend )
static const char *modes[] = { MODE_READ, MODE_READ,
MODE_WRITE, MODE_READWRITE };
#pragma convlit( resume )
const char *openMode;
char fileNameBuffer[ MAX_PATH_LENGTH ];
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( fileName != NULL );
assert( mode != 0 );
/* Initialise the stream structure */
memset( stream, 0, sizeof( STREAM ) );
stream->type = STREAM_TYPE_FILE;
if( ( mode & FILE_RW_MASK ) == FILE_READ )
stream->flags = STREAM_FLAG_READONLY;
openMode = modes[ mode & FILE_RW_MASK ];
/* Try and open the file */
fileName = bufferToEbcdic( fileNameBuffer, fileName );
stream->filePtr = fopen( fileName, openMode );
if( stream->filePtr == NULL )
/* The open failed, determine whether it was because the file doesn't
exist or because we can't use that access mode. An errno value
of ENOENT results from a ddname not found, and 67 (no mnemonic
name defined by IBM for DYNALLOC return codes) is member not
found, and 49 is data set not found */
return( ( errno == ENOENT || errno == 67 || errno == 49 ) ? \
CRYPT_ERROR_NOTFOUND : CRYPT_ERROR_OPEN );
return( CRYPT_OK );
}
#else
#ifndef STDIN_FILENO /* Usually defined in unistd.h */
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#endif /* STDIN_FILENO */
static int openFile( STREAM *stream, const char *fileName,
const int flags, const int mode )
{
int fd, count = 0;
/* A malicious user could have exec()'d us after closing standard I/O
handles (which we inherit across the exec()), which means that any
new files that we open will be allocated the same handles as the
former standard I/O ones. This could cause private data to be
written to stdout or error messages emitted by the calling app to go
into the opened file. To avoid this, we retry the open if we get the
same handle as a standard I/O one */
do
{
fd = open( fileName, flags, mode );
if( fd == -1 )
{
/* If we're creating the file, the only error condition is a
straight open error */
if( flags & O_CREAT )
return( CRYPT_ERROR_OPEN );
/* Determine whether the open failed because the file doesn't
exist or because we can't use that access mode */
return( ( access( fileName, 0 ) == -1 ) ? \
CRYPT_ERROR_NOTFOUND : CRYPT_ERROR_OPEN );
}
}
while( count++ < 3 && \
( fd == STDIN_FILENO || fd == STDOUT_FILENO || \
fd == STDERR_FILENO ) );
stream->fd = fd;
return( CRYPT_OK );
}
int sFileOpen( STREAM *stream, const char *fileName, const int mode )
{
#if defined( EBCDIC_CHARS )
#pragma convlit( suspend )
#endif /* EBCDIC_CHARS */
static const int modes[] = { O_RDONLY, O_RDONLY, O_WRONLY, O_RDWR };
#if defined( EBCDIC_CHARS )
#pragma convlit( resume )
#endif /* EBCDIC_CHARS */
int openMode = modes[ mode & FILE_RW_MASK ];
#ifdef EBCDIC_CHARS
char fileNameBuffer[ MAX_PATH_LENGTH ];
#endif /* EBCDIC_CHARS */
#ifdef USE_FCNTL_LOCKING
struct flock flockInfo;
#endif /* USE_FCNTL_LOCKING */
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( fileName != NULL );
assert( mode != 0 );
/* Initialise the stream structure */
memset( stream, 0, sizeof( STREAM ) );
stream->type = STREAM_TYPE_FILE;
if( ( mode & FILE_RW_MASK ) == FILE_READ )
stream->flags = STREAM_FLAG_READONLY;
/* If we're trying to write to the file, check whether we've got
permission to do so */
if( ( mode & FILE_WRITE ) && fileReadonly( fileName ) )
return( CRYPT_ERROR_PERMISSION );
#ifdef EBCDIC_CHARS
fileName = bufferToEbcdic( fileNameBuffer, fileName );
#endif /* EBCDIC_CHARS */
/* Defending against writing through links is somewhat difficult since
there's no atomic way to do this. What we do is lstat() the file,
open it as appropriate, and if it's an existing file ftstat() it and
compare various important fields to make sure that the file wasn't
changed between the lstat() and the open(). If everything is OK, we
then use the lstat() information to make sure that it isn't a symlink
(or at least that it's a normal file) and that the link count is 1.
These checks also catch other weird things like STREAMS stuff
fattach()'d over files. If these checks pass and the file already
exists we truncate it to mimic the effect of an open with create */
if( ( mode & FILE_RW_MASK ) == FILE_WRITE )
{
struct stat lstatInfo;
int status;
/* lstat() the file. If it doesn't exist, create it with O_EXCL. If
it does exist, open it for read/write and perform the fstat()
check */
if( lstat( fileName, &lstatInfo ) == -1 )
{
/* If the lstat() failed for reasons other than the file not
existing, return a file open error */
if( errno != ENOENT )
return( CRYPT_ERROR_OPEN );
/* The file doesn't exist, create it with O_EXCL to make sure
that an attacker can't slip in a file between the lstat() and
open() */
status = openFile( stream, fileName, O_CREAT | O_EXCL | O_RDWR,
0600 );
if( cryptStatusError( status ) )
return( status );
}
else
{
struct stat fstatInfo;
/* Open an existing file */
status = openFile( stream, fileName, O_RDWR, 0 );
if( cryptStatusError( status ) )
return( status );
/* fstat() the opened file and check that the file mode bits and
inode and device match */
if( fstat( stream->fd, &fstatInfo ) == -1 || \
lstatInfo.st_mode != fstatInfo.st_mode || \
lstatInfo.st_ino != fstatInfo.st_ino || \
lstatInfo.st_dev != fstatInfo.st_dev )
{
close( stream->fd );
return( CRYPT_ERROR_OPEN );
}
/* If the above check was passed, we know that the lstat() and
fstat() were done to the same file. Now check that there's
only one link, and that it's a normal file (this isn't
strictly necessary because the fstat() vs. lstat() st_mode
check would also find this). This also catches tricks like
an attacker closing stdin/stdout so that a newly-opened file
ends up with those file handles, with the result that the app
using cryptlib ends up corrupting cryptlib's files when it
sends data to stdout. In order to counter this we could
simply repeatedly open /dev/null until we get a handle > 2,
but the fstat() check will catch this in a manner that's also
safe with systems that don't have a stdout (so the handle > 2
check won't make much sense) */
if( fstatInfo.st_nlink > 1 || !S_ISREG( lstatInfo.st_mode ) )
{
close( stream->fd );
return( CRYPT_ERROR_OPEN );
}
/* Turn the file into an empty file */
ftruncate( stream->fd, 0 );
}
}
else
{
int status;
/* Open an existing file for read access */
status = openFile( stream, fileName, openMode, 0 );
if( cryptStatusError( status ) )
return( status );
}
/* Set the file access permissions so that only the owner can access it */
if( mode & FILE_PRIVATE )
chmod( fileName, 0600 );
/* Lock the file if possible to make sure that no-one else tries to do
things to it. If available we used the (BSD-style) flock(), if not we
fall back to Posix fcntl() locking (both mechanisms are broken, but
flock() is less broken). fcntl() locking has two disadvantages over
flock():
1. Locking is per-process rather than per-thread (specifically it's
based on processes and inodes rather than flock()'s file table
entries, for which any new handles created via dup()/fork()/open()
all refer to the same file table entry so there's a single location
at which to handle locking), so another thread in the same process
could still access the file. Whether this is a good thing or not
is context-dependant: We want multiple threads to be able to read
from the file (if one keyset handle is shared among threads), but
not necessarily for multiple threads to be able to write. We could
if necessary use mutexes for per-thread lock synchronisation, but
this gets incredibly ugly since we then have to duplicate parts of
the the system file table with per-thread mutexes, mess around with
an fstat() on each file access to determine if we're accessing an
already-open file, wrap all that up in more mutexes, etc etc, as
well as being something that's symtomatic of a user application bug
rather than normal behaviour that we can defend against.
2. Closing *any* descriptor for an fcntl()-locked file releases *all*
locks on the file (!!) (one manpage appropriately describes this
behaviour as "the completely stupid semantics of System V and IEEE
Std 1003.1-1988 (= POSIX.1)"). In other words if two threads or
processes open an fcntl()-locked file for shared read access then
the first close of the file releases all locks on it. Since
fcntl() requires a file handle to work, the only way to determine
whether a file is locked requires opening it, but as soon as we
close it again (for example to abort the access if there's a lock
on it) all locks are released.
flock() sticks with the much more sensible 4.2BSD-based last-close
semantics, however it doesn't usually work with NFS unless special
hacks have been applied. fcntl() passes lock requests to rpc.lockd
to handle, but this is its own type of mess since it's often
unreliable, so it's really not much worse than flock(). In addition
locking support under filesystems like AFS is often nonexistant, with
the lock apparently succeeding but no lock actually being applied.
Finally, locking is almost always advisory only, but even mandatory
locking can be bypassed by tricks such as copying the original,
unlinking it, and renaming the copy back to the original (the
unlinked - and still locked - original goes away once the handle is
closed) - this mechanism is standard practice for many Unix utilities
like text editors. In addition mandatory locking is wierd in that an
open for write (or read, on a write-locked file) will succeed, it's
only a later attempt to read/write that will fail.
This mess is why dotfile-locking is still so popular, but that's
probably going a bit far for simple keyset accesses */
#ifndef USE_FCNTL_LOCKING
if( flock( stream->fd, ( mode & FILE_EXCLUSIVE_ACCESS ) ? \
LOCK_EX | LOCK_NB : LOCK_SH | LOCK_NB ) == -1 && \
errno == EWOULDBLOCK )
{
close( stream->fd );
return( CRYPT_ERROR_PERMISSION );
}
#else
memset( &flockInfo, 0, sizeof( struct flock ) );
flockInfo.l_type = ( mode & FILE_EXCLUSIVE_ACCESS ) ? \
F_WRLCK : F_RDLCK;
flockInfo.l_whence = SEEK_SET;
flockInfo.l_start = flockInfo.l_len = 0;
if( fcntl( stream->fd, F_SETLK, flockInfo ) == -1 && \
( errno == EACCES || errno == EDEADLK ) )
{
/* Now we're in a bind. If we close the file and exit, the lock
we've just detected on the file is released (see the comment on
this utter braindamage above). OTOH if we don't close the file
we'll leak the file handle, which is bad for long-running
processes. Feedback from users indicates that leaking file
handles is less desirable than the possiblity of having the file
unlocked during an update (the former is a situation that occurs
far more frequently than the latter), so we close the handle and
hope that the update by the other process completes quickly */
close( stream->fd );
return( CRYPT_ERROR_PERMISSION );
}
#endif /* flock() vs. fcntl() locking */
return( CRYPT_OK );
}
#endif /* MVS USS special-case handling */
int sFileClose( STREAM *stream )
{
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( stream->type == STREAM_TYPE_FILE );
/* Unlock the file if necessary. If we're using fcntl() locking there's
no need to unlock the file since all locks are automatically released
as soon as any handle to it is closed (see the long comment above for
more on this complete braindamage) */
#ifndef USE_FCNTL_LOCKING
flock( stream->fd, LOCK_UN );
#endif /* !USE_FCNTL_LOCKING */
close( stream->fd );
zeroise( stream, sizeof( STREAM ) );
return( CRYPT_OK );
}
/* Read/write a block of data from/to a file stream */
int fileRead( STREAM *stream, void *buffer, const int length )
{
int bytesRead;
if( ( bytesRead = read( stream->fd, buffer, length ) ) == -1 )
return( CRYPT_ERROR_READ );
return( bytesRead );
}
int fileWrite( STREAM *stream, const void *buffer, const int length )
{
if( write( stream->fd, buffer, length ) != length )
return( CRYPT_ERROR_WRITE );
return( CRYPT_OK );
}
/* Commit data in a file stream to backing storage */
int fileFlush( STREAM *stream )
{
return( fsync( stream->fd ) == 0 ? CRYPT_OK : CRYPT_ERROR_WRITE );
}
/* Change the read/write position in a file */
int fileSeek( STREAM *stream, const long position )
{
#if defined( DDNAME_IO )
/* If we're using ddnames, we only seek if we're not already at the
start of the file to prevent postioning to 0 in a new empty PDS
member, which fails */
if( ( stream->bufCount > 0 || stream->bufPos > 0 || position > 0 ) )
/* Drop through */
#endif /* MVS USS special-case */
if( lseek( stream->fd, position, SEEK_SET ) == ( off_t ) -1 )
return( CRYPT_ERROR_WRITE );
return( CRYPT_OK );
}
/* Check whether a file is writeable */
BOOLEAN fileReadonly( const char *fileName )
{
#ifdef EBCDIC_CHARS
char fileNameBuffer[ MAX_PATH_LENGTH ];
fileName = bufferToEbcdic( fileNameBuffer, fileName );
#endif /* EBCDIC_CHARS */
#if defined( DDNAME_IO )
/* Requires a RACF check to determine this */
return( FALSE );
#else
if( access( fileName, W_OK ) == -1 && errno != ENOENT )
return( TRUE );
#endif /* OS-specific file accessibility check */
return( FALSE );
}
/* File deletion functions: Wipe a file from the current position to EOF,
and wipe and delete a file (although it's not terribly rigorous
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -