upx/src/file.cpp

395 lines
11 KiB
C++

/* file.cpp --
This file is part of the UPX executable compressor.
Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer
Copyright (C) 1996-2025 Laszlo Molnar
All Rights Reserved.
UPX and the UCL library are free software; you can redistribute them
and/or modify them under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, 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; see the file COPYING.
If not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Markus F.X.J. Oberhumer Laszlo Molnar
<markus@oberhumer.com> <ezerotven+github@gmail.com>
*/
#include "conf.h"
#include "file.h"
/*************************************************************************
// static file-related util functions; will throw on error
**************************************************************************/
/*static*/ void FileBase::chmod(const char *name, int mode) {
assert(name != nullptr && name[0] != 0);
#if HAVE_CHMOD
if (::chmod(name, mode) != 0)
throwIOException(name, errno);
#else
UNUSED(name);
UNUSED(mode);
// no error
#endif
}
/*static*/ void FileBase::rename(const char *old_, const char *new_) {
#if (ACC_OS_DOS32) && defined(__DJGPP__)
if (::_rename(old_, new_) != 0)
#else
if (::rename(old_, new_) != 0)
#endif
throwIOException("rename error", errno);
}
/*static*/ bool FileBase::unlink_noexcept(const char *name) noexcept {
assert_noexcept(name != nullptr && name[0] != 0);
bool success = ::unlink(name) == 0;
#if HAVE_CHMOD
if (!success)
success = (::chmod(name, 0666) == 0 && ::unlink(name) == 0);
#endif
return success;
}
/*static*/ void FileBase::unlink(const char *name) {
if (!unlink_noexcept(name))
throwIOException(name, errno);
}
/*************************************************************************
// FileBase
**************************************************************************/
FileBase::~FileBase() may_throw {
#if 0 && defined(__GNUC__) // debug
if (isOpen())
fprintf(stderr, "%s: %s\n", _name, __PRETTY_FUNCTION__);
#endif
if (std::uncaught_exceptions() == 0)
closex(); // may_throw
else
close_noexcept(); // currently in exception unwinding, use noexcept variant
}
bool FileBase::do_sopen() {
if (_shflags < 0)
_fd = ::open(_name, _flags, _mode);
else {
#if (ACC_OS_DOS32) && defined(__DJGPP__)
_fd = ::open(_name, _flags | _shflags, _mode);
#elif (ACC_ARCH_M68K && ACC_OS_TOS && ACC_CC_GNUC) && defined(__MINT__)
_fd = ::open(_name, _flags | (_shflags & O_SHMODE), _mode);
#elif defined(SH_DENYRW)
_fd = ::sopen(_name, _flags, _shflags, _mode);
#else
throwInternalError("bad usage of do_sopen()");
#endif
}
if (_fd < 0)
return false;
st.st_size = 0;
if (::fstat(_fd, &st) != 0)
throwIOException(_name, errno);
_length = st.st_size;
return true;
}
bool FileBase::close_noexcept() noexcept {
bool ok = true;
if (isOpen() && _fd != STDIN_FILENO && _fd != STDOUT_FILENO && _fd != STDERR_FILENO)
if (::close(_fd) == -1)
ok = false;
_fd = -1;
_flags = 0;
_mode = 0;
_name = nullptr;
_offset = 0;
_length = 0;
return ok;
}
void FileBase::closex() may_throw {
if (!close_noexcept())
throwIOException("close failed", errno);
}
// Return value of ::seek is the resulting file offset (same as ::tell())
upx_off_t FileBase::seek(upx_off_t off, int whence) {
if (!isOpen())
throwIOException("bad seek 1");
if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check
throwIOException("bad seek");
if (whence == SEEK_SET) {
if (off < 0)
throwIOException("bad seek 2");
off += _offset;
} else if (whence == SEEK_END) {
if (off > 0)
throwIOException("bad seek 3");
off += _offset + _length;
whence = SEEK_SET;
} else if (whence == SEEK_CUR) {
} else
throwInternalError("bad seek: whence");
upx_off_t l = ::lseek(_fd, off, whence);
if (l < 0)
throwIOException("seek error", errno);
return l - _offset;
}
upx_off_t FileBase::tell() const {
if (!isOpen())
throwIOException("bad tell");
upx_off_t l = ::lseek(_fd, 0, SEEK_CUR);
if (l < 0)
throwIOException("tell error", errno);
return l - _offset;
}
void FileBase::set_extent(upx_off_t offset, upx_off_t length) {
_offset = offset;
_length = length;
}
upx_off_t FileBase::st_size() const { return _length; }
/*************************************************************************
// InputFile
**************************************************************************/
void InputFile::sopen(const char *name, int flags, int shflags) {
closex();
_name = name;
_flags = flags;
_shflags = shflags;
_mode = 0;
_offset = 0;
_length = 0;
if (!super::do_sopen()) {
if (errno == ENOENT)
throw FileNotFoundException(_name, errno);
else if (errno == EEXIST)
throw FileAlreadyExistsException(_name, errno);
else
throwIOException(_name, errno);
}
_length_orig = _length;
}
int InputFile::read(SPAN_P(void) buf, upx_int64_t blen) {
if (!isOpen() || blen < 0)
throwIOException("bad read");
int len = (int) mem_size(1, blen); // sanity check
errno = 0;
long l = acc_safe_hread(_fd, raw_bytes(buf, len), len);
if (errno)
throwIOException("read error", errno);
return (int) l;
}
int InputFile::readx(SPAN_P(void) buf, upx_int64_t blen) {
int l = this->read(buf, blen);
if (l != blen)
throwEOFException();
return l;
}
upx_off_t InputFile::seek(upx_off_t off, int whence) {
upx_off_t pos = super::seek(off, whence);
if (_length < pos)
throwIOException("bad seek 4");
return pos;
}
upx_off_t InputFile::st_size_orig() const { return _length_orig; }
int InputFile::dupFd() may_throw {
if (!isOpen())
throwIOException("bad dup");
#if defined(HAVE_DUP) && (HAVE_DUP + 0 == 0)
errno = ENOSYS;
int r = -1;
#else
int r = ::dup(getFd());
#endif
if (r < 0)
throwIOException("dup", errno);
return r;
}
/*************************************************************************
// OutputFile
**************************************************************************/
void OutputFile::sopen(const char *name, int flags, int shflags, int mode) {
closex();
_name = name;
_flags = flags;
_shflags = shflags;
_mode = mode;
_offset = 0;
_length = 0;
if (!super::do_sopen()) {
#if 0
// don't throw FileNotFound here -- this is confusing
if (errno == ENOENT)
throw FileNotFoundException(_name,errno);
else
#endif
if (errno == EEXIST)
throw FileAlreadyExistsException(_name, errno);
else
throwIOException(_name, errno);
}
}
bool OutputFile::openStdout(int flags, bool force) {
closex();
int fd = STDOUT_FILENO;
if (!force && acc_isatty(fd))
return false;
_name = "<stdout>";
_flags = flags;
_shflags = -1;
_mode = 0;
_offset = 0;
_length = 0;
if (flags && acc_set_binmode(fd, 1) == -1)
throwIOException(_name, errno);
_fd = fd;
return true;
}
void OutputFile::write(SPAN_0(const void) buf, upx_int64_t blen) {
if (!isOpen() || blen < 0)
throwIOException("bad write");
// allow nullptr if blen == 0
if (blen == 0)
return;
int len = (int) mem_size(1, blen); // sanity check
errno = 0;
#if WITH_XSPAN >= 2
NO_fprintf(stderr, "write %p %zd (%p) %d\n", buf.raw_ptr(), buf.raw_size_in_bytes(),
buf.raw_base(), len);
#endif
long l = acc_safe_hwrite(_fd, raw_bytes(buf, len), len);
if (l != len)
throwIOException("write error", errno);
bytes_written += len;
#if TESTING && 0
static upx_std_atomic(bool) dumping;
if (!dumping) {
dumping = true;
char fn[64];
static int part = 0;
snprintf(fn, sizeof(fn), "upx-dump-%04d.data", part++);
OutputFile::dump(fn, buf, len);
dumping = false;
}
#endif
}
upx_off_t OutputFile::st_size() const {
if (opt->to_stdout) { // might be a pipe ==> .st_size is invalid
return bytes_written; // too big if seek()+write() instead of rewrite()
}
struct stat my_st;
my_st.st_size = 0;
if (::fstat(_fd, &my_st) != 0)
throwIOException(_name, errno);
return my_st.st_size;
}
void OutputFile::rewrite(SPAN_P(const void) buf, int len) {
assert(!opt->to_stdout);
write(buf, len);
bytes_written -= len; // restore
}
upx_off_t OutputFile::seek(upx_off_t off, int whence) {
if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check
throwIOException("bad seek");
assert(!opt->to_stdout);
switch (whence) {
case SEEK_SET:
if (bytes_written < off)
bytes_written = off;
_length = bytes_written; // cheap, lazy update; needed?
break;
case SEEK_END:
_length = bytes_written; // necessary
break;
}
return super::seek(off, whence);
}
// WARNING: fsync() does not exist in some Windows environments.
// This trick works only on UNIX-like systems.
// int OutputFile::read(void *buf, int len) {
// fsync(_fd);
// InputFile infile;
// infile.open(this->getName(), O_RDONLY | O_BINARY);
// infile.seek(this->tell(), SEEK_SET);
// return infile.read(buf, len);
//}
void OutputFile::set_extent(upx_off_t offset, upx_off_t length) {
super::set_extent(offset, length);
bytes_written = 0;
if (0 == offset && 0xffffffffLL == length) { // TODO: check all callers of this method
st.st_size = 0;
if (::fstat(_fd, &st) != 0)
throwIOException(_name, errno);
_length = st.st_size - offset;
}
}
upx_off_t OutputFile::unset_extent() {
upx_off_t l = ::lseek(_fd, 0, SEEK_END);
if (l < 0)
throwIOException("lseek error", errno);
_offset = 0;
_length = l;
bytes_written = _length;
return _length;
}
/*static*/ void OutputFile::dump(const char *name, SPAN_P(const void) buf, int len, int flags) {
if (flags < 0)
flags = O_CREAT | O_TRUNC;
flags |= O_WRONLY | O_BINARY;
OutputFile f;
f.open(name, flags, 0600);
f.write(raw_bytes(buf, len), len);
f.closex();
}
/*************************************************************************
//
**************************************************************************/
TEST_CASE("file") {
InputFile fi;
CHECK(!fi.isOpen());
CHECK(fi.getFd() == -1);
CHECK(fi.st_size() == 0);
OutputFile fo;
CHECK(!fo.isOpen());
CHECK(fo.getFd() == -1);
CHECK(fo.getBytesWritten() == 0);
}
/* vim:set ts=4 sw=4 et: */