mirror of
https://github.com/upx/upx.git
synced 2024-11-23 04:39:59 +00:00
src: add new option --link
to preserve hard links; use with care
This commit is contained in:
parent
62dbf8485f
commit
4f6320d3aa
1
NEWS
1
NEWS
@ -4,6 +4,7 @@ User visible changes for UPX
|
||||
|
||||
Changes in 4.2.0 (XX XXX 2023):
|
||||
* bug fixes - see https://github.com/upx/upx/milestone/13
|
||||
* new option '--link' to preserve hard-links (Unix only; use with care)
|
||||
* add support for NO_COLOR env var; see https://no-color.org/
|
||||
|
||||
Changes in 4.1.0 (08 Aug 2023):
|
||||
|
@ -724,7 +724,7 @@ extern const char *progname;
|
||||
bool main_set_exit_code(int ec);
|
||||
int main_get_options(int argc, char **argv);
|
||||
void main_get_envoptions();
|
||||
int upx_main(int argc, char *argv[]);
|
||||
int upx_main(int argc, char *argv[]) may_throw;
|
||||
|
||||
// msg.cpp
|
||||
void printSetNl(int need_nl) noexcept;
|
||||
@ -741,8 +741,8 @@ void infoHeader();
|
||||
void infoWriting(const char *what, upx_int64_t size);
|
||||
|
||||
// work.cpp
|
||||
void do_one_file(const char *iname, char *oname);
|
||||
int do_files(int i, int argc, char *argv[]);
|
||||
void do_one_file(const char *iname, char *oname) may_throw;
|
||||
int do_files(int i, int argc, char *argv[]) may_throw;
|
||||
|
||||
// help.cpp
|
||||
extern const char gitrev[];
|
||||
|
20
src/file.cpp
20
src/file.cpp
@ -33,6 +33,7 @@
|
||||
**************************************************************************/
|
||||
|
||||
/*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);
|
||||
@ -51,9 +52,16 @@
|
||||
throwIOException("rename error", errno);
|
||||
}
|
||||
|
||||
/*static*/ void FileBase::unlink(const char *name) {
|
||||
if (::unlink(name) != 0)
|
||||
/*static*/ bool FileBase::unlink(const char *name, bool check) {
|
||||
assert(name != nullptr && name[0] != 0);
|
||||
bool success = ::unlink(name) == 0;
|
||||
#if HAVE_CHMOD
|
||||
if (!success)
|
||||
success = (::chmod(name, 0666) == 0 && ::unlink(name) == 0);
|
||||
#endif
|
||||
if (check && !success)
|
||||
throwIOException(name, errno);
|
||||
return success;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
@ -122,14 +130,14 @@ upx_off_t FileBase::seek(upx_off_t off, int whence) {
|
||||
if (off < 0)
|
||||
throwIOException("bad seek 2");
|
||||
off += _offset;
|
||||
}
|
||||
if (whence == SEEK_END) {
|
||||
} else if (whence == SEEK_END) {
|
||||
if (off > 0)
|
||||
throwIOException("bad seek 3");
|
||||
off += _offset + _length;
|
||||
whence = SEEK_SET;
|
||||
}
|
||||
// SEEK_CUR falls through to here
|
||||
} 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);
|
||||
|
@ -50,9 +50,9 @@ public:
|
||||
|
||||
public:
|
||||
// static file-related util functions; will throw on error
|
||||
static void chmod(const char *name, int mode);
|
||||
static void rename(const char *old_, const char *new_);
|
||||
static void unlink(const char *name);
|
||||
static void chmod(const char *name, int mode) may_throw;
|
||||
static void rename(const char *old_, const char *new_) may_throw;
|
||||
static bool unlink(const char *name, bool check = true) may_throw;
|
||||
|
||||
protected:
|
||||
bool do_sopen();
|
||||
|
14
src/help.cpp
14
src/help.cpp
@ -185,7 +185,6 @@ void show_help(int verbose) {
|
||||
con_fprintf(f,
|
||||
" -q be quiet -v be verbose\n"
|
||||
" -oFILE write output to 'FILE'\n"
|
||||
//" -f force overwrite of output files and compression of suspicious files\n"
|
||||
" -f force compression of suspicious files\n"
|
||||
"%s%s"
|
||||
, (verbose == 0) ? " -k keep backup files\n" : ""
|
||||
@ -222,6 +221,19 @@ void show_help(int verbose) {
|
||||
" --overlay=skip don't compress a file with an overlay\n"
|
||||
"\n");
|
||||
fg = con_fg(f, FG_YELLOW);
|
||||
con_fprintf(f, "File system options:\n");
|
||||
fg = con_fg(f, fg);
|
||||
con_fprintf(f,
|
||||
" --force-overwrite force overwrite of output files\n"
|
||||
#if defined(__unix__) && !defined(__MSYS2__)
|
||||
" --link preserve hard links (Unix only) [USE WITH CARE]\n"
|
||||
" --no-link do not preserve hard links but rename files [default]\n"
|
||||
#endif
|
||||
" --no-mode do not preserve file mode (aka permissions)\n"
|
||||
" --no-owner do not preserve file ownership\n"
|
||||
" --no-time do not preserve file timestamp\n"
|
||||
"\n");
|
||||
fg = con_fg(f, FG_YELLOW);
|
||||
con_fprintf(f, "Options for djgpp2/coff:\n");
|
||||
fg = con_fg(f, fg);
|
||||
con_fprintf(f,
|
||||
|
24
src/main.cpp
24
src/main.cpp
@ -160,7 +160,7 @@ static void check_not_both(bool e1, bool e2, const char *c1, const char *c2) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_options(int i, int argc) {
|
||||
static void check_and_update_options(int i, int argc) {
|
||||
assert(i <= argc);
|
||||
|
||||
if (opt->cmd != CMD_COMPRESS) {
|
||||
@ -179,6 +179,7 @@ static void check_options(int i, int argc) {
|
||||
opt->overlay = opt->COPY_OVERLAY;
|
||||
|
||||
check_not_both(opt->exact, opt->overlay == opt->STRIP_OVERLAY, "--exact", "--overlay=strip");
|
||||
check_not_both(opt->force_overwrite, opt->preserve_link, "--force-overwrite", "--link");
|
||||
|
||||
// set default backup option
|
||||
if (opt->backup < 0)
|
||||
@ -198,6 +199,13 @@ static void check_options(int i, int argc) {
|
||||
e_usage();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__unix__) && !defined(__MSYS2__)
|
||||
#else
|
||||
// preserve_link is currently silently ignored on non-Unix platforms
|
||||
// (we may revisit this decision later if there is some actual use-case)
|
||||
opt->preserve_link = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
@ -521,6 +529,14 @@ static int do_option(int optc, const char *arg) {
|
||||
case 519:
|
||||
opt->no_env = true;
|
||||
break;
|
||||
case 530:
|
||||
// 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
|
||||
opt->preserve_link = true;
|
||||
break;
|
||||
case 531:
|
||||
opt->preserve_link = false;
|
||||
break;
|
||||
case 526:
|
||||
opt->preserve_mode = false;
|
||||
break;
|
||||
@ -797,8 +813,10 @@ int main_get_options(int argc, char **argv) {
|
||||
{"force", 0, N, 'f'}, // force overwrite of output files
|
||||
{"force-compress", 0, N, 'f'}, // and compression of suspicious files
|
||||
{"force-overwrite", 0x90, N, 529}, // force overwrite of output files
|
||||
{"link", 0x90, N, 530}, // preserve hard link
|
||||
{"info", 0, N, 'i'}, // info mode
|
||||
{"no-env", 0x10, N, 519}, // no environment var
|
||||
{"no-link", 0x90, N, 531}, // do not preserve hard link [default]
|
||||
{"no-mode", 0x10, N, 526}, // do not preserve mode (permissions)
|
||||
{"no-owner", 0x10, N, 527}, // do not preserve ownership
|
||||
{"no-progress", 0, N, 516}, // no progress bar
|
||||
@ -1147,7 +1165,7 @@ static void first_options(int argc, char **argv) {
|
||||
// main entry point
|
||||
**************************************************************************/
|
||||
|
||||
int upx_main(int argc, char *argv[]) {
|
||||
int upx_main(int argc, char *argv[]) may_throw {
|
||||
int i;
|
||||
static char default_argv0[] = "upx";
|
||||
assert(argc >= 1); // sanity check
|
||||
@ -1251,7 +1269,7 @@ int upx_main(int argc, char *argv[]) {
|
||||
if (argc == 1)
|
||||
e_help();
|
||||
set_term(stderr);
|
||||
check_options(i, argc);
|
||||
check_and_update_options(i, argc);
|
||||
int num_files = argc - i;
|
||||
if (num_files < 1) {
|
||||
if (opt->verbose >= 2)
|
||||
|
@ -81,6 +81,7 @@ struct Options final {
|
||||
bool no_env;
|
||||
bool no_progress;
|
||||
const char *output_name;
|
||||
bool preserve_link;
|
||||
bool preserve_mode;
|
||||
bool preserve_ownership;
|
||||
bool preserve_timestamp;
|
||||
|
191
src/work.cpp
191
src/work.cpp
@ -34,6 +34,7 @@
|
||||
#include "file.h"
|
||||
#include "packmast.h"
|
||||
#include "ui.h"
|
||||
#include "util/membuffer.h"
|
||||
|
||||
#if (ACC_OS_DOS32) && defined(__DJGPP__)
|
||||
#define USE_FTIME 1
|
||||
@ -50,16 +51,94 @@
|
||||
#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 WronlyOpenMode { WOM_MUST_EXIST_TRUNCATE, WOM_MUST_CREATE, WOM_CREATE_OR_TRUNCATE };
|
||||
|
||||
static constexpr int get_wronly_open_flags(WronlyOpenMode mode) noexcept {
|
||||
constexpr int flags = O_WRONLY | O_BINARY;
|
||||
if (mode == WOM_MUST_EXIST_TRUNCATE)
|
||||
return flags | O_TRUNC; // will cause an error if file does not exist
|
||||
if (mode == WOM_MUST_CREATE)
|
||||
return flags | O_CREAT | O_EXCL; // will cause an error if file already exists
|
||||
// create if not exists, otherwise truncate
|
||||
return flags | O_CREAT | O_TRUNC;
|
||||
}
|
||||
|
||||
static void copy_file_contents(const char *iname, const char *oname, WronlyOpenMode mode)
|
||||
may_throw {
|
||||
InputFile fi;
|
||||
fi.sopen(iname, O_RDONLY | O_BINARY, SH_DENYWR);
|
||||
fi.seek(0, SEEK_SET);
|
||||
int flags = get_wronly_open_flags(mode);
|
||||
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(oname);
|
||||
UNUSED(preserve_mode);
|
||||
UNUSED(preserve_ownership);
|
||||
UNUSED(preserve_timestamp);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
// process one file
|
||||
**************************************************************************/
|
||||
|
||||
void do_one_file(const char *iname, char *oname) {
|
||||
void do_one_file(const char *const iname, char *const oname) may_throw {
|
||||
int r;
|
||||
struct stat st;
|
||||
struct stat st; // stat of iname
|
||||
mem_clear(&st);
|
||||
#if HAVE_LSTAT
|
||||
r = lstat(iname, &st);
|
||||
@ -99,6 +178,7 @@ void do_one_file(const char *iname, char *oname) {
|
||||
throwIOException("file is write protected -- skipped");
|
||||
}
|
||||
|
||||
// open input file
|
||||
InputFile fi;
|
||||
fi.sopen(iname, O_RDONLY | O_BINARY, SH_DENYWR);
|
||||
|
||||
@ -113,6 +193,7 @@ void do_one_file(const char *iname, char *oname) {
|
||||
|
||||
// open output file
|
||||
OutputFile fo;
|
||||
bool copy_timestamp_only = false;
|
||||
if (opt->cmd == CMD_COMPRESS || opt->cmd == CMD_DECOMPRESS) {
|
||||
if (opt->to_stdout) {
|
||||
if (!fo.openStdout(1, opt->force ? true : false))
|
||||
@ -121,33 +202,30 @@ void do_one_file(const char *iname, char *oname) {
|
||||
char tname[ACC_FN_PATH_MAX + 1];
|
||||
if (opt->output_name) {
|
||||
strcpy(tname, opt->output_name);
|
||||
if (opt->force_overwrite || opt->force >= 2) {
|
||||
#if HAVE_CHMOD
|
||||
r = chmod(tname, 0777);
|
||||
IGNORE_ERROR(r);
|
||||
#endif
|
||||
r = unlink(tname);
|
||||
IGNORE_ERROR(r);
|
||||
}
|
||||
if ((opt->force_overwrite || opt->force >= 2) && !opt->preserve_link)
|
||||
FileBase::unlink(tname, false);
|
||||
} else {
|
||||
if (!maketempname(tname, sizeof(tname), iname, ".upx"))
|
||||
throwIOException("could not create a temporary file name");
|
||||
}
|
||||
int flags = O_CREAT | O_WRONLY | O_BINARY;
|
||||
if (opt->force_overwrite || opt->force)
|
||||
flags |= O_TRUNC;
|
||||
else
|
||||
flags |= O_EXCL;
|
||||
int flags = get_wronly_open_flags(WOM_MUST_CREATE);
|
||||
if (opt->output_name && opt->preserve_link) {
|
||||
flags = get_wronly_open_flags(WOM_CREATE_OR_TRUNCATE);
|
||||
if (file_exists(opt->output_name)) {
|
||||
flags = get_wronly_open_flags(WOM_MUST_EXIST_TRUNCATE);
|
||||
copy_timestamp_only = true;
|
||||
}
|
||||
} else if (opt->force_overwrite || opt->force)
|
||||
flags = get_wronly_open_flags(WOM_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 = 0600;
|
||||
if (!opt->preserve_mode)
|
||||
omode = 0666;
|
||||
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);
|
||||
@ -187,59 +265,45 @@ void do_one_file(const char *iname, char *oname) {
|
||||
fo.closex();
|
||||
fi.closex();
|
||||
|
||||
// rename or delete files
|
||||
// rename or copy files
|
||||
// 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
|
||||
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");
|
||||
FileBase::rename(iname, bakname);
|
||||
if (opt->preserve_link) {
|
||||
copy_file_contents(iname, bakname, WOM_MUST_CREATE);
|
||||
copy_file_attributes(&st, bakname, true, true, true);
|
||||
copy_file_contents(oname, iname, WOM_MUST_EXIST_TRUNCATE);
|
||||
FileBase::unlink(oname);
|
||||
copy_timestamp_only = true;
|
||||
} else {
|
||||
FileBase::rename(iname, bakname);
|
||||
FileBase::rename(oname, iname);
|
||||
}
|
||||
} else if (opt->preserve_link) {
|
||||
copy_file_contents(oname, iname, WOM_MUST_EXIST_TRUNCATE);
|
||||
FileBase::unlink(oname);
|
||||
copy_timestamp_only = true;
|
||||
} else {
|
||||
#if HAVE_CHMOD
|
||||
r = chmod(iname, 0777);
|
||||
IGNORE_ERROR(r);
|
||||
#endif
|
||||
FileBase::unlink(iname);
|
||||
FileBase::rename(oname, 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;
|
||||
UNUSED(name);
|
||||
#if USE_UTIME
|
||||
// copy time stamp
|
||||
if (opt->preserve_timestamp) {
|
||||
struct utimbuf u;
|
||||
u.actime = st.st_atime;
|
||||
u.modtime = st.st_mtime;
|
||||
r = utime(name, &u);
|
||||
IGNORE_ERROR(r);
|
||||
}
|
||||
#endif
|
||||
#if HAVE_CHOWN
|
||||
// copy the group ownership
|
||||
if (opt->preserve_ownership) {
|
||||
r = chown(name, -1, st.st_gid);
|
||||
IGNORE_ERROR(r);
|
||||
}
|
||||
#endif
|
||||
#if HAVE_CHMOD
|
||||
// copy permissions
|
||||
if (opt->preserve_mode) {
|
||||
r = chmod(name, st.st_mode);
|
||||
IGNORE_ERROR(r);
|
||||
}
|
||||
#endif
|
||||
#if HAVE_CHOWN
|
||||
// copy the user ownership
|
||||
if (opt->preserve_ownership) {
|
||||
r = chown(name, st.st_uid, -1);
|
||||
IGNORE_ERROR(r);
|
||||
}
|
||||
#endif
|
||||
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();
|
||||
@ -251,17 +315,12 @@ void do_one_file(const char *iname, char *oname) {
|
||||
|
||||
static void unlink_ofile(char *oname) noexcept {
|
||||
if (oname && oname[0]) {
|
||||
#if HAVE_CHMOD
|
||||
int r;
|
||||
r = chmod(oname, 0777);
|
||||
IGNORE_ERROR(r);
|
||||
#endif
|
||||
if (unlink(oname) == 0)
|
||||
oname[0] = 0; // done with oname
|
||||
FileBase::unlink(oname, false);
|
||||
oname[0] = 0; // done with oname
|
||||
}
|
||||
}
|
||||
|
||||
int do_files(int i, int argc, char *argv[]) {
|
||||
int do_files(int i, int argc, char *argv[]) may_throw {
|
||||
upx_compiler_sanity_check();
|
||||
if (opt->verbose >= 1) {
|
||||
show_header();
|
||||
@ -271,7 +330,7 @@ int do_files(int i, int argc, char *argv[]) {
|
||||
for (; i < argc; i++) {
|
||||
infoHeader();
|
||||
|
||||
const char *iname = argv[i];
|
||||
const char *const iname = argv[i];
|
||||
char oname[ACC_FN_PATH_MAX + 1];
|
||||
oname[0] = 0;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user