Coverage Report

Created: 2026-09-03 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/upx/src/file.cpp
Line
Count
Source
1
/* file.cpp --
2
3
   This file is part of the UPX executable compressor.
4
5
   Copyright (C) Markus Franz Xaver Johannes Oberhumer
6
   Copyright (C) Laszlo Molnar
7
   All Rights Reserved.
8
9
   UPX and the UCL library are free software; you can redistribute them
10
   and/or modify them under the terms of the GNU General Public License as
11
   published by the Free Software Foundation; either version 2 of
12
   the License, or (at your option) any later version.
13
14
   This program is distributed in the hope that it will be useful,
15
   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
   GNU General Public License for more details.
18
19
   You should have received a copy of the GNU General Public License
20
   along with this program; see the file COPYING.
21
   If not, write to the Free Software Foundation, Inc.,
22
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24
   Markus F.X.J. Oberhumer              Laszlo Molnar
25
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>
26
 */
27
28
#include "conf.h"
29
#include "file.h"
30
31
/*************************************************************************
32
// static file-related util functions; will throw on error
33
**************************************************************************/
34
35
0
/*static*/ void FileBase::chmod(const char *name, int mode) {
36
0
    assert(name != nullptr && name[0] != 0);
37
0
#if HAVE_CHMOD
38
0
    if (::chmod(name, mode) != 0)
39
0
        throwIOException(name, errno);
40
#else
41
    UNUSED(name);
42
    UNUSED(mode);
43
    // no error
44
#endif
45
0
}
46
47
0
/*static*/ void FileBase::rename(const char *old_, const char *new_) {
48
#if (ACC_OS_DOS32) && defined(__DJGPP__)
49
    if (::_rename(old_, new_) != 0)
50
#else
51
0
    if (::rename(old_, new_) != 0)
52
0
#endif
53
0
        throwIOException("rename error", errno);
54
0
}
55
56
15.3k
/*static*/ bool FileBase::unlink_noexcept(const char *name) noexcept {
57
15.3k
    assert_noexcept(name != nullptr && name[0] != 0);
58
0
    bool success = ::unlink(name) == 0;
59
15.3k
#if HAVE_CHMOD
60
15.3k
    if (!success)
61
0
        success = (::chmod(name, 0666) == 0 && ::unlink(name) == 0);
62
15.3k
#endif
63
15.3k
    return success;
64
15.3k
}
65
66
0
/*static*/ void FileBase::unlink(const char *name) {
67
0
    if (!unlink_noexcept(name))
68
0
        throwIOException(name, errno);
69
0
}
70
71
/*************************************************************************
72
// FileBase
73
**************************************************************************/
74
75
61.5k
FileBase::~FileBase() may_throw {
76
#if 0 && defined(__GNUC__) // debug
77
    if (isOpen())
78
        fprintf(stderr, "%s: %s\n", _name, __PRETTY_FUNCTION__);
79
#endif
80
61.5k
    if (std::uncaught_exceptions() == 0)
81
30.9k
        closex(); // may_throw
82
30.6k
    else
83
30.6k
        close_noexcept(); // currently in exception unwinding, use noexcept variant
84
61.5k
}
85
86
30.7k
bool FileBase::do_sopen() {
87
30.7k
    if (_shflags < 0)
88
30.7k
        _fd = ::open(_name, _flags, _mode);
89
0
    else {
90
#if (ACC_OS_DOS32) && defined(__DJGPP__)
91
        _fd = ::open(_name, _flags | _shflags, _mode);
92
#elif (ACC_ARCH_M68K && ACC_OS_TOS && ACC_CC_GNUC) && defined(__MINT__)
93
        _fd = ::open(_name, _flags | (_shflags & O_SHMODE), _mode);
94
#elif defined(SH_DENYRW)
95
        _fd = ::sopen(_name, _flags, _shflags, _mode);
96
#else
97
0
        throwInternalError("bad usage of do_sopen()");
98
0
#endif
99
0
    }
100
30.7k
    if (_fd < 0)
101
0
        return false;
102
30.7k
    st.st_size = 0;
103
30.7k
    if (::fstat(_fd, &st) != 0)
104
0
        throwIOException(_name, errno);
105
30.7k
    _length = st.st_size;
106
30.7k
    return true;
107
30.7k
}
108
109
92.5k
bool FileBase::close_noexcept() noexcept {
110
92.5k
    bool ok = true;
111
92.5k
    if (isOpen() && _fd != STDIN_FILENO && _fd != STDOUT_FILENO && _fd != STDERR_FILENO)
112
30.7k
        if (::close(_fd) == -1)
113
0
            ok = false;
114
92.5k
    _fd = -1;
115
92.5k
    _flags = 0;
116
92.5k
    _mode = 0;
117
92.5k
    _name = nullptr;
118
92.5k
    _offset = 0;
119
92.5k
    _length = 0;
120
92.5k
    return ok;
121
92.5k
}
122
123
61.9k
void FileBase::closex() may_throw {
124
61.9k
    if (!close_noexcept())
125
0
        throwIOException("close failed", errno);
126
61.9k
}
127
128
// Return value of ::seek is the resulting file offset (same as ::tell())
129
941k
upx_off_t FileBase::seek(upx_off_t off, int whence) {
130
941k
    if (!isOpen())
131
0
        throwIOException("bad seek 1");
132
941k
    if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check
133
420
        throwIOException("bad seek");
134
941k
    if (whence == SEEK_SET) {
135
909k
        if (off < 0)
136
81
            throwIOException("bad seek 2");
137
908k
        off += _offset;
138
908k
    } else if (whence == SEEK_END) {
139
29.6k
        if (off > 0)
140
0
            throwIOException("bad seek 3");
141
29.6k
        off += _offset + _length;
142
29.6k
        whence = SEEK_SET;
143
29.6k
    } else if (whence == SEEK_CUR) {
144
2.71k
    } else
145
0
        throwInternalError("bad seek: whence");
146
941k
    upx_off_t l = ::lseek(_fd, off, whence);
147
941k
    if (l < 0)
148
0
        throwIOException("seek error", errno);
149
941k
    return l - _offset;
150
941k
}
151
152
235
upx_off_t FileBase::tell() const {
153
235
    if (!isOpen())
154
0
        throwIOException("bad tell");
155
235
    upx_off_t l = ::lseek(_fd, 0, SEEK_CUR);
156
235
    if (l < 0)
157
0
        throwIOException("tell error", errno);
158
235
    return l - _offset;
159
235
}
160
161
194
void FileBase::set_extent(upx_off_t offset, upx_off_t length) {
162
194
    _offset = offset;
163
194
    _length = length;
164
194
}
165
166
535k
upx_off_t FileBase::st_size() const { return _length; }
167
168
/*************************************************************************
169
// InputFile
170
**************************************************************************/
171
172
15.3k
void InputFile::sopen(const char *name, int flags, int shflags) {
173
15.3k
    closex();
174
15.3k
    _name = name;
175
15.3k
    _flags = flags;
176
15.3k
    _shflags = shflags;
177
15.3k
    _mode = 0;
178
15.3k
    _offset = 0;
179
15.3k
    _length = 0;
180
15.3k
    if (!super::do_sopen()) {
181
0
        if (errno == ENOENT)
182
0
            throw FileNotFoundException(_name, errno);
183
0
        else if (errno == EEXIST)
184
0
            throw FileAlreadyExistsException(_name, errno);
185
0
        else
186
0
            throwIOException(_name, errno);
187
0
    }
188
15.3k
    _length_orig = _length;
189
15.3k
}
190
191
620k
int InputFile::read(SPAN_P(void) buf, upx_int64_t blen) {
192
620k
    if (!isOpen() || blen < 0)
193
1
        throwIOException("bad read");
194
620k
    int len = (int) mem_size(1, blen); // sanity check
195
620k
#if WITH_XSPAN >= 2
196
620k
    NO_fprintf(stderr, "read %p %zu (%p) %d\n", buf.raw_ptr(), buf.raw_size_in_bytes(),
197
620k
               buf.raw_base(), len);
198
620k
#endif
199
620k
    errno = 0;
200
620k
    long l = acc_safe_hread(_fd, raw_bytes(buf, len), len);
201
620k
    if (errno)
202
0
        throwIOException("read error", errno);
203
620k
    return (int) l;
204
620k
}
205
206
612k
int InputFile::readx(SPAN_P(void) buf, upx_int64_t blen) {
207
612k
    int l = this->read(buf, blen);
208
612k
    if (l != blen)
209
17.1k
        throwEOFException();
210
594k
    return l;
211
612k
}
212
213
939k
upx_off_t InputFile::seek(upx_off_t off, int whence) {
214
939k
    upx_off_t pos = super::seek(off, whence);
215
939k
    if (_length < pos)
216
15.0k
        throwIOException("bad seek 4");
217
924k
    return pos;
218
939k
}
219
220
431
upx_off_t InputFile::st_size_orig() const { return _length_orig; }
221
222
0
int InputFile::dupFd() may_throw {
223
0
    if (!isOpen())
224
0
        throwIOException("bad dup");
225
#if defined(HAVE_DUP) && (HAVE_DUP + 0 == 0)
226
    errno = ENOSYS;
227
    int r = -1;
228
#else
229
0
    int r = ::dup(getFd());
230
0
#endif
231
0
    if (r < 0)
232
0
        throwIOException("dup", errno);
233
0
    return r;
234
0
}
235
236
/*************************************************************************
237
// OutputFile
238
**************************************************************************/
239
240
15.3k
void OutputFile::sopen(const char *name, int flags, int shflags, int mode) {
241
15.3k
    closex();
242
15.3k
    _name = name;
243
15.3k
    _flags = flags;
244
15.3k
    _shflags = shflags;
245
15.3k
    _mode = mode;
246
15.3k
    _offset = 0;
247
15.3k
    _length = 0;
248
15.3k
    if (!super::do_sopen()) {
249
#if 0
250
        // don't throw FileNotFound here -- this is confusing
251
        if (errno == ENOENT)
252
            throw FileNotFoundException(_name,errno);
253
        else
254
#endif
255
0
        if (errno == EEXIST)
256
0
            throw FileAlreadyExistsException(_name, errno);
257
0
        else
258
0
            throwIOException(_name, errno);
259
0
    }
260
15.3k
}
261
262
0
bool OutputFile::openStdout(int flags, bool force) {
263
0
    closex();
264
0
    int fd = STDOUT_FILENO;
265
0
    if (!force && acc_isatty(fd))
266
0
        return false;
267
0
    _name = "<stdout>";
268
0
    _flags = flags;
269
0
    _shflags = -1;
270
0
    _mode = 0;
271
0
    _offset = 0;
272
0
    _length = 0;
273
0
    if (flags && acc_set_binmode(fd, 1) == -1)
274
0
        throwIOException(_name, errno);
275
0
    _fd = fd;
276
0
    return true;
277
0
}
278
279
32.8k
void OutputFile::write(SPAN_0(const void) buf, upx_int64_t blen) {
280
32.8k
    if (!isOpen() || blen < 0)
281
0
        throwIOException("bad write");
282
    // allow nullptr if blen == 0
283
32.8k
    if (blen == 0)
284
10
        return;
285
32.8k
    int len = (int) mem_size(1, blen); // sanity check
286
32.8k
#if WITH_XSPAN >= 2
287
32.8k
    NO_fprintf(stderr, "write %p %zu (%p) %d\n", buf.raw_ptr(), buf.raw_size_in_bytes(),
288
32.8k
               buf.raw_base(), len);
289
32.8k
#endif
290
32.8k
    errno = 0;
291
32.8k
    long l = acc_safe_hwrite(_fd, raw_bytes(buf, len), len);
292
32.8k
    if (l != len)
293
0
        throwIOException("write error", errno);
294
32.8k
    bytes_written += len;
295
#if TESTING && 0
296
    static upx_std_atomic(bool) dumping;
297
    if (!dumping) {
298
        dumping = true;
299
        char fn[64];
300
        static int part = 0;
301
        snprintf(fn, sizeof(fn), "upx-dump-%04d.data", part++);
302
        OutputFile::dump(fn, buf, len);
303
        dumping = false;
304
    }
305
#endif
306
32.8k
}
307
308
186
void OutputFile::rewrite(SPAN_P(const void) buf, upx_int64_t blen) {
309
186
    assert(!opt->to_stdout);
310
0
    write(buf, blen);
311
186
    bytes_written -= blen; // restore
312
186
}
313
314
0
upx_off_t OutputFile::st_size() const {
315
0
    if (opt->to_stdout) {     // might be a pipe ==> .st_size is invalid
316
0
        return bytes_written; // too big if seek()+write() instead of rewrite()
317
0
    }
318
0
    struct stat my_st;
319
0
    my_st.st_size = 0;
320
0
    if (::fstat(_fd, &my_st) != 0)
321
0
        throwIOException(_name, errno);
322
0
    return my_st.st_size;
323
0
}
324
325
1.89k
upx_off_t OutputFile::seek(upx_off_t off, int whence) {
326
1.89k
    if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check
327
1
        throwIOException("bad seek");
328
1.89k
    assert(!opt->to_stdout);
329
0
    switch (whence) {
330
1.89k
    case SEEK_SET:
331
1.89k
        if (bytes_written < off)
332
282
            bytes_written = off;
333
1.89k
        _length = bytes_written; // cheap, lazy update; needed?
334
1.89k
        break;
335
0
    case SEEK_END:
336
0
        _length = bytes_written; // necessary
337
0
        break;
338
1.89k
    }
339
1.89k
    return super::seek(off, whence);
340
1.89k
}
341
342
// WARNING: fsync() does not exist in some Windows environments.
343
// This trick works only on UNIX-like systems.
344
// int OutputFile::read(void *buf, int len) {
345
//    fsync(_fd);
346
//    InputFile infile;
347
//    infile.open(this->getName(), O_RDONLY | O_BINARY);
348
//    infile.seek(this->tell(), SEEK_SET);
349
//    return infile.read(buf, len);
350
//}
351
352
18
void OutputFile::set_extent(upx_off_t offset, upx_off_t length) {
353
18
    super::set_extent(offset, length);
354
18
    bytes_written = 0;
355
18
    if (0 == offset && 0xffffffffLL == length) { // TODO: check all callers of this method
356
0
        st.st_size = 0;
357
0
        if (::fstat(_fd, &st) != 0)
358
0
            throwIOException(_name, errno);
359
0
        _length = st.st_size - offset;
360
0
    }
361
18
}
362
363
18
upx_off_t OutputFile::unset_extent() {
364
18
    upx_off_t l = ::lseek(_fd, 0, SEEK_END);
365
18
    if (l < 0)
366
0
        throwIOException("lseek error", errno);
367
18
    _offset = 0;
368
18
    _length = l;
369
18
    bytes_written = _length;
370
18
    return _length;
371
18
}
372
373
/*static*/
374
0
void OutputFile::dump(const char *name, SPAN_P(const void) buf, upx_int64_t blen, int flags) {
375
0
    if (flags < 0)
376
0
        flags = O_CREAT | O_TRUNC;
377
0
    flags |= O_WRONLY | O_BINARY;
378
0
    OutputFile f;
379
0
    f.open(name, flags, 0600);
380
0
    f.write(buf, blen);
381
0
    f.closex();
382
0
}
383
384
/*************************************************************************
385
//
386
**************************************************************************/
387
388
15.4k
TEST_CASE("file") {
389
15.4k
    InputFile fi;
390
15.4k
    CHECK(!fi.isOpen());
391
15.4k
    CHECK(fi.getFd() == -1);
392
15.4k
    CHECK(fi.st_size() == 0);
393
15.4k
    OutputFile fo;
394
15.4k
    CHECK(!fo.isOpen());
395
15.4k
    CHECK(fo.getFd() == -1);
396
15.4k
    CHECK(fo.getBytesWritten() == 0);
397
15.4k
}
398
399
/* vim:set ts=4 sw=4 et: */