PosixStorage.cxx revision 7c478bd95313f5f23a4c958a745db2134aa03244
// Copyright (c) 1994, 1995 James Clark
// See the file COPYING for copying permission.
#pragma ident "%Z%%M% %I% %E% SMI"
#ifdef __GNUG__
#pragma implementation
#endif
#include "splib.h"
#include "PosixStorage.h"
#include "RewindStorageObject.h"
#include "StorageManager.h"
#include "DescriptorManager.h"
#include "MessageArg.h"
#include "ErrnoMessageArg.h"
#include "SearchResultMessageArg.h"
#include "Message.h"
#include "StringC.h"
#include "StringOf.h"
#include "CharsetInfo.h"
#include "CodingSystem.h"
#include "macros.h"
#include "PosixStorageMessages.h"
#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#ifdef SP_INCLUDE_IO_H
#include <io.h> // for open, fstat, lseek, read prototypes
#endif
#ifdef SP_INCLUDE_UNISTD_H
#include <unistd.h>
#endif
#ifdef SP_INCLUDE_OSFCN_H
#include <osfcn.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stddef.h>
#ifndef S_ISREG
#ifndef S_IFREG
#define S_IFREG _S_IFREG
#endif
#ifndef S_IFMT
#define S_IFMT _S_IFMT
#endif
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif /* not S_ISREG */
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifdef SP_WIDE_SYSTEM
#include <windows.h>
#endif
#ifdef SP_NAMESPACE
namespace SP_NAMESPACE {
#endif
#ifdef SP_WIDE_SYSTEM
typedef wchar_t FChar;
#else
typedef char FChar;
#endif
class PosixBaseStorageObject : public RewindStorageObject {
public:
PosixBaseStorageObject(int fd, Boolean mayRewind);
size_t getBlockSize() const;
protected:
enum { defaultBlockSize = 8192 };
int fd_;
PackedBoolean eof_;
Boolean seekToStart(Messenger &);
virtual Boolean seek(off_t, Messenger &) = 0;
static int xclose(int fd);
private:
Boolean canSeek(int fd);
off_t startOffset_;
};
PosixBaseStorageObject::PosixBaseStorageObject(int fd, Boolean mayRewind)
: fd_(fd), eof_(0),
RewindStorageObject(mayRewind, mayRewind && canSeek(fd))
{
}
Boolean PosixBaseStorageObject::canSeek(int fd)
{
struct stat sb;
if (fstat(fd, &sb) < 0 || !S_ISREG(sb.st_mode)
|| (startOffset_ = lseek(fd, off_t(0), SEEK_CUR)) < 0)
return 0;
else
return 1;
}
Boolean PosixBaseStorageObject::seekToStart(Messenger &mgr)
{
eof_ = 0;
return seek(startOffset_, mgr);
}
int PosixBaseStorageObject::xclose(int fd)
{
int ret;
do {
ret = ::close(fd);
} while (ret < 0 && errno == EINTR);
return ret;
}
class PosixStorageObject : public PosixBaseStorageObject, private DescriptorUser {
public:
PosixStorageObject(int fd,
const StringC &,
const String<FChar> &,
Boolean mayRewind,
DescriptorManager *);
~PosixStorageObject();
Boolean read(char *buf, size_t bufSize, Messenger &mgr, size_t &nread);
Boolean suspend();
Boolean seek(off_t, Messenger &);
void willNotRewind();
private:
void resume(Messenger &);
PackedBoolean suspended_;
off_t suspendPos_;
const MessageType2 *suspendFailedMessage_;
int suspendErrno_;
StringC filename_;
String<FChar> cfilename_;
void systemError(Messenger &, const MessageType2 &, int);
};
inline int openFile(const FChar *s) {
#ifdef SP_WIDE_SYSTEM
int fd = _wopen(s, O_RDONLY|O_BINARY);
if (fd < 0 && errno != ENOENT) {
String<char> buf;
int len = WideCharToMultiByte(CP_ACP, 0, s, -1, 0, 0, 0, 0);
buf.resize(len + 1);
WideCharToMultiByte(CP_ACP, 0, s, -1, buf.begin(), len, 0, 0);
buf[len] = '\0';
return ::open(buf.data(), O_RDONLY|O_BINARY);
}
return fd;
#else
return ::open(s, O_RDONLY|O_BINARY);
#endif
}
PosixStorageManager::PosixStorageManager(const char *type,
const CharsetInfo *filenameCharset,
#ifndef SP_WIDE_SYSTEM
const OutputCodingSystem *filenameCodingSystem,
#endif
int maxFDs)
: IdStorageManager(filenameCharset),
type_(type),
#ifndef SP_WIDE_SYSTEM
filenameCodingSystem_(filenameCodingSystem),
#endif
descriptorManager_(maxFDs)
{
Char newline = idCharset()->execToDesc('\n');
reString_.assign(&newline, 1);
}
const char *PosixStorageManager::type() const
{
return type_;
}
void PosixStorageManager::addSearchDir(const StringC &str)
{
searchDirs_.push_back(str);
}
#ifdef SP_POSIX_FILENAMES
#define FILENAME_TYPE_DEFINED
// FIXME should use idCharset.
Boolean PosixStorageManager::isAbsolute(const StringC &file) const
{
return file.size() > 0 && file[0] == '/';
}
StringC PosixStorageManager::extractDir(const StringC &str) const
{
for (size_t i = str.size(); i > 0; i--)
if (str[i - 1] == '/')
return StringC(str.data(), i); // include slash for root case
return StringC();
}
StringC PosixStorageManager::combineDir(const StringC &dir,
const StringC &base) const
{
StringC result(dir);
if (dir.size() > 0 && dir[dir.size() - 1] != '/')
result += '/';
result += base;
return result;
}
Boolean PosixStorageManager::transformNeutral(StringC &str, Boolean fold,
Messenger &) const
{
if (fold)
for (size_t i = 0; i < str.size(); i++) {
Char c = str[i];
if (c <= (unsigned char)-1)
str[i] = tolower(str[i]);
}
return 1;
}
#endif /* SP_POSIX_FILENAMES */
#ifdef SP_MSDOS_FILENAMES
#define FILENAME_TYPE_DEFINED
Boolean PosixStorageManager::isAbsolute(const StringC &s) const
{
if (s.size() == 0)
return 0;
return s[0] == '/' || s[0] == '\\' || (s.size() > 1 && s[1] == ':');
}
StringC PosixStorageManager::extractDir(const StringC &str) const
{
for (size_t i = str.size(); i > 0; i--)
if (str[i - 1] == '/' || str[i - 1] == '\\'
|| (i == 2 && str[i - 1] == ':'))
return StringC(str.data(), i); // include separator
return StringC();
}
StringC PosixStorageManager::combineDir(const StringC &dir,
const StringC &base) const
{
StringC result(dir);
if (dir.size() > 0) {
Char lastChar = dir[dir.size() - 1];
if (lastChar != '/' && lastChar != '\\'
&& !(dir.size() == 2 && lastChar == ':'))
result += '\\';
}
result += base;
return result;
}
Boolean PosixStorageManager::transformNeutral(StringC &str, Boolean,
Messenger &) const
{
for (size_t i = 0; i < str.size(); i++)
if (str[i] == '/')
str[i] = '\\';
return 1;
}
#endif /* SP_MSDOS_FILENAMES */
#ifdef SP_MAC_FILENAMES
// Colons separate directory names
// relative path-names start with a colon, or have no colons
// absolute path-names don't start with a colon and have at least one colon
#define FILENAME_TYPE_DEFINED
Boolean PosixStorageManager::isAbsolute(const StringC &s) const
{
if (s.size() == 0)
return 0;
if (s[0] == ':')
return 0; // starts with a colon => relative
size_t ss = s.size();
for (size_t i = 0; i < ss; i++)
if (s[i] == ':')
return 1; // absolute
return 0; // no colons => relative
}
StringC PosixStorageManager::extractDir(const StringC &str) const
{
for (size_t i = str.size(); i > 0; i--)
if (str[i - 1] == ':')
return StringC(str.data(), i); // include separator
return StringC();
}
StringC PosixStorageManager::combineDir(const StringC &dir,
const StringC &base) const
{
StringC result(dir);
if (dir.size() > 0) {
Char lastChar = dir[dir.size() - 1];
if (lastChar != ':')
result += ':';
}
if (base[0] == ':') {
StringC newbase(base.data() + 1,base.size() - 1);
result += newbase;
}
else result += base;
return result;
}
Boolean PosixStorageManager::transformNeutral(StringC &str, Boolean,
Messenger &) const
{
if (str[0] == '/') { // absolute
StringC nstr(str.data() + 1,str.size()-1);
str = nstr;
}
else { // relative
Char cc = ':';
StringC colon(&cc,1);
str.insert(0,colon);
}
for (size_t i = 0; i < str.size(); i++)
if (str[i] == '/')
str[i] = ':';
return 1;
}
#endif /* SP_MAC_FILENAMES */
#ifndef FILENAME_TYPE_DEFINED
Boolean PosixStorageManager::isAbsolute(const StringC &) const
{
return 1;
}
StringC PosixStorageManager::extractDir(const StringC &) const
{
return StringC();
}
StringC PosixStorageManager::combineDir(const StringC &,
const StringC &base) const
{
return base;
}
Boolean PosixStorageManager::transformNeutral(StringC &, Boolean,
Messenger &) const
{
return 1;
}
#endif /* not FILENAME_TYPE_DEFINED */
Boolean PosixStorageManager::resolveRelative(const StringC &baseId,
StringC &specId,
Boolean search) const
{
if (isAbsolute(specId))
return 1;
if (!search || searchDirs_.size() == 0) {
specId = combineDir(extractDir(baseId), specId);
return 1;
}
return 0;
}
StorageObject *
PosixStorageManager::makeStorageObject(const StringC &spec,
const StringC &base,
Boolean search,
Boolean mayRewind,
Messenger &mgr,
StringC &found)
{
if (spec.size() == 0) {
mgr.message(PosixStorageMessages::invalidFilename,
StringMessageArg(spec));
return 0;
}
descriptorManager_.acquireD();
Boolean absolute = isAbsolute(spec);
SearchResultMessageArg sr;
for (size_t i = 0; i < searchDirs_.size() + 1; i++) {
StringC filename;
if (absolute)
filename = spec;
else if (i == 0)
filename = combineDir(extractDir(base), spec);
else
filename = combineDir(searchDirs_[i - 1], spec);
#ifdef SP_WIDE_SYSTEM
String<FChar> cfilename(filename);
cfilename += FChar(0);
#else
String<FChar> cfilename = filenameCodingSystem_->convertOut(filename);
#endif
int fd;
do {
fd = openFile(cfilename.data());
} while (fd < 0 && errno == EINTR);
if (fd >= 0) {
found = filename;
return new PosixStorageObject(fd,
filename,
cfilename,
mayRewind,
&descriptorManager_);
}
int savedErrno = errno;
if (absolute || !search || searchDirs_.size() == 0) {
ParentLocationMessenger(mgr).message(PosixStorageMessages::openSystemCall,
StringMessageArg(filename),
ErrnoMessageArg(savedErrno));
descriptorManager_.releaseD();
return 0;
}
sr.add(filename, savedErrno);
}
descriptorManager_.releaseD();
ParentLocationMessenger(mgr).message(PosixStorageMessages::cannotFind,
StringMessageArg(spec), sr);
return 0;
}
PosixStorageObject::PosixStorageObject(int fd,
const StringC &filename,
const String<FChar> &cfilename,
Boolean mayRewind,
DescriptorManager *manager)
: DescriptorUser(manager),
PosixBaseStorageObject(fd, mayRewind),
suspended_(0),
filename_(filename),
cfilename_(cfilename)
{
}
PosixStorageObject::~PosixStorageObject()
{
if (fd_ >= 0) {
(void)xclose(fd_);
releaseD();
}
}
Boolean PosixStorageObject::seek(off_t off, Messenger &mgr)
{
if (lseek(fd_, off, SEEK_SET) < 0) {
fd_ = -1;
systemError(mgr, PosixStorageMessages::lseekSystemCall, errno);
return 0;
}
else
return 1;
}
Boolean PosixStorageObject::read(char *buf, size_t bufSize, Messenger &mgr,
size_t &nread)
{
if (readSaved(buf, bufSize, nread))
return 1;
if (suspended_)
resume(mgr);
if (fd_ < 0 || eof_)
return 0;
long n;
do {
n = ::read(fd_, buf, bufSize);
} while (n < 0 && errno == EINTR);
if (n > 0) {
nread = size_t(n);
saveBytes(buf, nread);
return 1;
}
if (n < 0) {
int saveErrno = errno;
releaseD();
(void)xclose(fd_);
systemError(mgr, PosixStorageMessages::readSystemCall, saveErrno);
fd_ = -1;
}
else {
eof_ = 1;
// n == 0, so end of file
if (!mayRewind_) {
releaseD();
if (xclose(fd_) < 0)
systemError(mgr, PosixStorageMessages::closeSystemCall, errno);
fd_ = -1;
}
}
return 0;
}
void PosixStorageObject::willNotRewind()
{
RewindStorageObject::willNotRewind();
if (eof_ && fd_ >= 0) {
releaseD();
(void)xclose(fd_);
fd_ = -1;
}
}
Boolean PosixStorageObject::suspend()
{
if (fd_ < 0 || suspended_)
return 0;
struct stat sb;
if (fstat(fd_, &sb) < 0 || !S_ISREG(sb.st_mode))
return 0;
suspendFailedMessage_ = 0;
suspendPos_ = lseek(fd_, 0, SEEK_CUR);
if (suspendPos_ == (off_t)-1) {
suspendFailedMessage_ = &PosixStorageMessages::lseekSystemCall;
suspendErrno_ = errno;
}
if (xclose(fd_) < 0 && !suspendFailedMessage_) {
suspendFailedMessage_ = &PosixStorageMessages::closeSystemCall;
suspendErrno_ = errno;
}
fd_ = -1;
suspended_ = 1;
releaseD();
return 1;
}
void PosixStorageObject::resume(Messenger &mgr)
{
ASSERT(suspended_);
if (suspendFailedMessage_) {
systemError(mgr, *suspendFailedMessage_, suspendErrno_);
suspended_ = 0;
return;
}
acquireD();
// suspended_ must be 1 until after acquireD() is called,
// so that we don't try to suspend this one before it is resumed.
suspended_ = 0;
do {
fd_ = openFile(cfilename_.data());
} while (fd_ < 0 && errno == EINTR);
if (fd_ < 0) {
releaseD();
systemError(mgr, PosixStorageMessages::openSystemCall, errno);
return;
}
if (lseek(fd_, suspendPos_, SEEK_SET) < 0) {
systemError(mgr, PosixStorageMessages::lseekSystemCall, errno);
(void)xclose(fd_);
fd_ = -1;
releaseD();
}
}
#ifdef SP_STAT_BLKSIZE
size_t PosixBaseStorageObject::getBlockSize() const
{
struct stat sb;
long sz;
if (fstat(fd_, &sb) < 0)
return defaultBlockSize;
if (!S_ISREG(sb.st_mode))
return defaultBlockSize;
if ((unsigned long)sb.st_blksize > size_t(-1))
sz = size_t(-1);
else
sz = sb.st_blksize;
return sz;
}
#else /* not SP_STAT_BLKSIZE */
size_t PosixBaseStorageObject::getBlockSize() const
{
return defaultBlockSize;
}
#endif /* not SP_STAT_BLKSIZE */
void PosixStorageObject::systemError(Messenger &mgr,
const MessageType2 &msg,
int err)
{
ParentLocationMessenger(mgr).message(msg,
StringMessageArg(filename_),
ErrnoMessageArg(err));
}
class PosixFdStorageObject : public PosixBaseStorageObject {
public:
PosixFdStorageObject(int, Boolean mayRewind);
Boolean read(char *buf, size_t bufSize, Messenger &mgr, size_t &nread);
Boolean seek(off_t, Messenger &);
enum {
noError,
readError,
invalidNumberError,
lseekError
};
private:
int origFd_;
};
PosixFdStorageManager::PosixFdStorageManager(const char *type,
const CharsetInfo *idCharset)
: IdStorageManager(idCharset), type_(type)
{
}
Boolean PosixFdStorageManager::inheritable() const
{
return 0;
}
StorageObject *PosixFdStorageManager::makeStorageObject(const StringC &id,
const StringC &,
Boolean,
Boolean mayRewind,
Messenger &mgr,
StringC &foundId)
{
int n = 0;
size_t i;
for (i = 0; i < id.size(); i++) {
UnivChar ch;
if (!idCharset()->descToUniv(id[i], ch))
break;
if (ch < UnivCharsetDesc::zero || ch > UnivCharsetDesc::zero + 9)
break;
int digit = ch - UnivCharsetDesc::zero;
// Allow the division to be done at compile-time.
if (n > INT_MAX/10)
break;
n *= 10;
if (n > INT_MAX - digit)
break;
n += digit;
}
if (i < id.size() || i == 0) {
mgr.message(PosixStorageMessages::invalidNumber,
StringMessageArg(id));
return 0;
}
foundId = id;
// Could check access mode with fcntl(n, F_GETFL).
return new PosixFdStorageObject(n, mayRewind);
}
PosixFdStorageObject::PosixFdStorageObject(int fd, Boolean mayRewind)
: PosixBaseStorageObject(fd, mayRewind), origFd_(fd)
{
}
const char *PosixFdStorageManager::type() const
{
return type_;
}
Boolean PosixFdStorageObject::read(char *buf, size_t bufSize, Messenger &mgr,
size_t &nread)
{
if (readSaved(buf, bufSize, nread))
return 1;
if (fd_ < 0 || eof_)
return 0;
long n;
do {
n = ::read(fd_, buf, bufSize);
} while (n < 0 && errno == EINTR);
if (n > 0) {
nread = size_t(n);
saveBytes(buf, nread);
return 1;
}
if (n < 0) {
ParentLocationMessenger(mgr).message(PosixStorageMessages::fdRead,
NumberMessageArg(fd_),
ErrnoMessageArg(errno));
fd_ = -1;
}
else
eof_ = 1;
return 0;
}
Boolean PosixFdStorageObject::seek(off_t off, Messenger &mgr)
{
if (lseek(fd_, off, SEEK_SET) < 0) {
ParentLocationMessenger(mgr).message(PosixStorageMessages::fdLseek,
NumberMessageArg(fd_),
ErrnoMessageArg(errno));
return 0;
}
else
return 1;
}
#ifdef SP_NAMESPACE
}
#endif