upx/src/work.cpp

421 lines
14 KiB
C++

/* work.cpp -- main driver
This file is part of the UPX executable compressor.
Copyright (C) 1996-2023 Markus Franz Xaver Johannes Oberhumer
Copyright (C) 1996-2023 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>
*/
// This file implements the central loop, and it uses class PackMaster to
// dispatch. PackMaster by itself will instantiate a concrete subclass
// of class PackerBase which then does the actual work.
// And see p_com.cpp for a simple executable format.
#include "conf.h"
#include "file.h"
#include "packmast.h"
#include "ui.h"
#include "util/membuffer.h"
#if (ACC_OS_DOS32) && defined(__DJGPP__)
#define USE_FTIME 1
#elif ((ACC_OS_WIN32 || ACC_OS_WIN64) && (ACC_CC_INTELC || ACC_CC_MSC))
#define USE__FUTIME 1
#elif HAVE_UTIME
#define USE_UTIME 1
#endif
#if !defined(SH_DENYRW)
#define SH_DENYRW (-1)
#endif
#if !defined(SH_DENYWR)
#define SH_DENYWR (-1)
#endif
/*************************************************************************
// util
**************************************************************************/
// ignore errors in some cases and silence __attribute__((__warn_unused_result__))
#define IGNORE_ERROR(var) ACC_UNUSED(var)
enum OpenMode { RO_MUST_EXIST, WO_MUST_EXIST_TRUNCATE, WO_MUST_CREATE, WO_CREATE_OR_TRUNCATE };
static constexpr int get_open_flags(OpenMode om) noexcept {
constexpr int wo_flags = O_WRONLY | O_BINARY;
if (om == WO_MUST_EXIST_TRUNCATE)
return wo_flags | O_TRUNC; // will cause an error if file does not exist
if (om == WO_MUST_CREATE)
return wo_flags | O_CREAT | O_EXCL; // will cause an error if file already exists
if (om == WO_CREATE_OR_TRUNCATE)
return wo_flags | O_CREAT | O_TRUNC; // create if not exists, otherwise truncate
// RO_MUST_EXIST
return O_RDONLY | O_BINARY; // will cause an error if file does not exist
}
static void copy_file_contents(const char *iname, const char *oname, OpenMode om) may_throw {
InputFile fi;
fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR);
fi.seek(0, SEEK_SET);
int flags = get_open_flags(om);
int shmode = SH_DENYWR;
int omode = 0600; // affected by umask; ignored unless O_CREAT
OutputFile fo;
fo.sopen(oname, flags, shmode, omode);
fo.seek(0, SEEK_SET);
MemBuffer buf(256 * 1024 * 1024);
for (;;) {
size_t bytes = fi.read(buf, buf.getSize());
if (bytes == 0)
break;
fo.write(buf, bytes);
}
fi.closex();
fo.closex();
}
static void copy_file_attributes(const struct stat *st, const char *oname, bool preserve_mode,
bool preserve_ownership, bool preserve_timestamp) noexcept {
#if USE_UTIME
// copy time stamp
if (preserve_timestamp) {
struct utimbuf u;
u.actime = st->st_atime;
u.modtime = st->st_mtime;
int r = utime(oname, &u);
IGNORE_ERROR(r);
}
#endif
#if HAVE_CHOWN
// copy the group ownership
if (preserve_ownership) {
int r = chown(oname, -1, st->st_gid);
IGNORE_ERROR(r);
}
#endif
#if HAVE_CHMOD
// copy permissions
if (preserve_mode) {
int r = chmod(oname, st->st_mode);
IGNORE_ERROR(r);
}
#endif
#if HAVE_CHOWN
// copy the user ownership
if (preserve_ownership) {
int r = chown(oname, st->st_uid, -1);
IGNORE_ERROR(r);
}
#endif
// maybe unused
UNUSED(st);
UNUSED(oname);
UNUSED(preserve_mode);
UNUSED(preserve_ownership);
UNUSED(preserve_timestamp);
}
/*************************************************************************
// process one file
**************************************************************************/
void do_one_file(const char *const iname, char *const oname) may_throw {
oname[0] = 0; // make empty
// check iname stat
struct stat st;
mem_clear(&st);
#if HAVE_LSTAT
int rr = lstat(iname, &st);
#else
int rr = stat(iname, &st);
#endif
if (rr != 0) {
if (errno == ENOENT)
throw FileNotFoundException(iname, errno);
else
throwIOException(iname, errno);
}
#if HAVE_LSTAT
if (S_ISLNK(st.st_mode))
throwIOException("is a symlink -- skipped");
#endif
if (S_ISDIR(st.st_mode))
throwIOException("is a directory -- skipped");
if (!(S_ISREG(st.st_mode)))
throwIOException("not a regular file -- skipped");
#if defined(__unix__)
// no special bits may be set
if ((st.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0)
throwIOException("file has special permissions -- skipped");
#endif
if (st.st_size <= 0)
throwIOException("empty file -- skipped");
if (st.st_size < 512)
throwIOException("file is too small -- skipped");
if (!mem_size_valid_bytes(st.st_size))
throwIOException("file is too large -- skipped");
if ((st.st_mode & S_IWUSR) == 0) {
bool skip = true;
if (opt->output_name)
skip = false;
else if (opt->to_stdout)
skip = false;
else if (opt->backup)
skip = false;
if (skip)
throwIOException("file is write protected -- skipped");
}
// open input file
InputFile fi;
fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR);
#if USE_FTIME
struct ftime fi_ftime;
mem_clear(&fi_ftime);
if (opt->preserve_timestamp) {
if (getftime(fi.getFd(), &fi_ftime) != 0)
throwIOException("cannot determine file timestamp");
}
#endif
// open output file
// NOTE: only use "preserve_link" if you really need it, e.g. it can fail
// with ETXTBSY and other unexpected errors; renaming files is much safer
OutputFile fo;
bool preserve_link = opt->preserve_link;
bool copy_timestamp_only = false;
if (opt->cmd == CMD_COMPRESS || opt->cmd == CMD_DECOMPRESS) {
if (opt->to_stdout) {
preserve_link = false; // not needed
if (!fo.openStdout(1, opt->force ? true : false))
throwIOException("data not written to a terminal; Use '-f' to force.");
} else {
char tname[ACC_FN_PATH_MAX + 1];
if (opt->output_name) {
strcpy(tname, opt->output_name);
if ((opt->force_overwrite || opt->force >= 2) && !preserve_link)
FileBase::unlink(tname, false);
} else {
if (st.st_nlink < 2)
preserve_link = false; // not needed
if (!maketempname(tname, sizeof(tname), iname, ".upx"))
throwIOException("could not create a temporary file name");
}
int flags = get_open_flags(WO_MUST_CREATE); // don't overwrite files by default
if (opt->output_name && preserve_link) {
flags = get_open_flags(WO_CREATE_OR_TRUNCATE);
#if HAVE_LSTAT
struct stat ost;
mem_clear(&ost);
int r = lstat(tname, &ost);
if (r == 0 && S_ISREG(ost.st_mode)) {
preserve_link = ost.st_nlink >= 2;
} else if (r == 0 && S_ISLNK(ost.st_mode)) {
// output_name is a symlink (valid or dangling)
FileBase::unlink(tname, false);
preserve_link = false; // not needed
} else {
preserve_link = false; // not needed
}
#endif
if (preserve_link) {
flags = get_open_flags(WO_MUST_EXIST_TRUNCATE);
copy_timestamp_only = true;
}
} else if (opt->force_overwrite || opt->force)
flags = get_open_flags(WO_CREATE_OR_TRUNCATE);
int shmode = SH_DENYWR;
#if (ACC_ARCH_M68K && ACC_OS_TOS && ACC_CC_GNUC) && defined(__MINT__)
// TODO later: check current mintlib if this hack is still needed
flags |= O_TRUNC;
shmode = O_DENYRW;
#endif
// cannot rely on open() because of umask
// int omode = st.st_mode | 0600;
int omode = opt->preserve_mode ? 0600 : 0666; // affected by umask; only for O_CREAT
fo.sopen(tname, flags, shmode, omode);
// open succeeded - now set oname[]
strcpy(oname, tname);
}
}
// handle command - actual work is here
PackMaster pm(&fi, opt);
if (opt->cmd == CMD_COMPRESS)
pm.pack(&fo);
else if (opt->cmd == CMD_DECOMPRESS)
pm.unpack(&fo);
else if (opt->cmd == CMD_TEST)
pm.test();
else if (opt->cmd == CMD_LIST)
pm.list();
else if (opt->cmd == CMD_FILEINFO)
pm.fileInfo();
else
throwInternalError("invalid command");
// copy time stamp
if (oname[0] && opt->preserve_timestamp && fo.isOpen()) {
#if USE_FTIME
int r = setftime(fo.getFd(), &fi_ftime);
IGNORE_ERROR(r);
#elif USE__FUTIME
struct _utimbuf u;
u.actime = st.st_atime;
u.modtime = st.st_mtime;
int r = _futime(fo.getFd(), &u);
IGNORE_ERROR(r);
#endif
}
// close files
fi.closex();
fo.closex();
// rename or copy files
if (oname[0] && !opt->output_name) {
// both iname and oname do exist; rename oname to iname
if (opt->backup) {
char bakname[ACC_FN_PATH_MAX + 1];
if (!makebakname(bakname, sizeof(bakname), iname))
throwIOException("could not create a backup file name");
if (preserve_link) {
copy_file_contents(iname, bakname, WO_MUST_CREATE);
copy_file_attributes(&st, bakname, true, true, true);
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE);
FileBase::unlink(oname);
copy_timestamp_only = true;
} else {
FileBase::rename(iname, bakname);
FileBase::rename(oname, iname);
}
} else if (preserve_link) {
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE);
FileBase::unlink(oname);
copy_timestamp_only = true;
} else {
FileBase::unlink(iname);
FileBase::rename(oname, iname);
}
// now iname is the new packed/unpacked file and oname does not exist any longer
}
// copy file attributes
if (oname[0]) {
oname[0] = 0; // done with oname
const char *name = opt->output_name ? opt->output_name : iname;
if (copy_timestamp_only)
copy_file_attributes(&st, name, false, false, opt->preserve_timestamp);
else
copy_file_attributes(&st, name, opt->preserve_mode, opt->preserve_ownership,
opt->preserve_timestamp);
}
UiPacker::uiConfirmUpdate();
}
/*************************************************************************
// process all files from the commandline
**************************************************************************/
static void unlink_ofile(char *oname) noexcept {
if (oname && oname[0]) {
FileBase::unlink(oname, false);
oname[0] = 0; // done with oname
}
}
int do_files(int i, int argc, char *argv[]) may_throw {
upx_compiler_sanity_check();
if (opt->verbose >= 1) {
show_header();
UiPacker::uiHeader();
}
for (; i < argc; i++) {
infoHeader();
const char *const iname = argv[i];
char oname[ACC_FN_PATH_MAX + 1];
oname[0] = 0;
try {
do_one_file(iname, oname);
} catch (const Exception &e) {
unlink_ofile(oname);
if (opt->verbose >= 1 || (opt->verbose >= 0 && !e.isWarning()))
printErr(iname, e);
main_set_exit_code(e.isWarning() ? EXIT_WARN : EXIT_ERROR);
// this is not fatal, continue processing more files
} catch (const Error &e) {
unlink_ofile(oname);
printErr(iname, e);
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
} catch (std::bad_alloc *e) {
unlink_ofile(oname);
printErr(iname, "out of memory");
UNUSED(e);
// delete e;
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
} catch (const std::bad_alloc &) {
unlink_ofile(oname);
printErr(iname, "out of memory");
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
} catch (std::exception *e) {
unlink_ofile(oname);
printUnhandledException(iname, e);
// delete e;
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
} catch (const std::exception &e) {
unlink_ofile(oname);
printUnhandledException(iname, &e);
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
} catch (...) {
unlink_ofile(oname);
printUnhandledException(iname, nullptr);
main_set_exit_code(EXIT_ERROR);
return -1; // fatal error
}
}
if (opt->cmd == CMD_COMPRESS)
UiPacker::uiPackTotal();
else if (opt->cmd == CMD_DECOMPRESS)
UiPacker::uiUnpackTotal();
else if (opt->cmd == CMD_LIST)
UiPacker::uiListTotal();
else if (opt->cmd == CMD_TEST)
UiPacker::uiTestTotal();
else if (opt->cmd == CMD_FILEINFO)
UiPacker::uiFileInfoTotal();
return 0;
}
/* vim:set ts=4 sw=4 et: */