Line | Count | Source |
1 | | /* file.cpp -- |
2 | | |
3 | | This file is part of the UPX executable compressor. |
4 | | |
5 | | Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer |
6 | | Copyright (C) 1996-2025 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 | 0 | /*static*/ bool FileBase::unlink_noexcept(const char *name) noexcept { |
57 | 0 | assert_noexcept(name != nullptr && name[0] != 0); |
58 | 0 | bool success = ::unlink(name) == 0; |
59 | 0 | #if HAVE_CHMOD |
60 | 0 | if (!success) |
61 | 0 | success = (::chmod(name, 0666) == 0 && ::unlink(name) == 0); |
62 | 0 | #endif |
63 | 0 | return success; |
64 | 0 | } |
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 | 22.8k | 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 | 22.8k | if (std::uncaught_exceptions() == 0) |
81 | 11.5k | closex(); // may_throw |
82 | 11.3k | else |
83 | 11.3k | close_noexcept(); // currently in exception unwinding, use noexcept variant |
84 | 22.8k | } |
85 | | |
86 | 5.70k | bool FileBase::do_sopen() { |
87 | 5.70k | if (_shflags < 0) |
88 | 5.70k | _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 | 5.70k | if (_fd < 0) |
101 | 0 | return false; |
102 | 5.70k | st.st_size = 0; |
103 | 5.70k | if (::fstat(_fd, &st) != 0) |
104 | 0 | throwIOException(_name, errno); |
105 | 5.70k | _length = st.st_size; |
106 | 5.70k | return true; |
107 | 5.70k | } |
108 | | |
109 | 28.6k | bool FileBase::close_noexcept() noexcept { |
110 | 28.6k | bool ok = true; |
111 | 28.6k | if (isOpen() && _fd != STDIN_FILENO && _fd != STDOUT_FILENO && _fd != STDERR_FILENO) |
112 | 5.70k | if (::close(_fd) == -1) |
113 | 0 | ok = false; |
114 | 28.6k | _fd = -1; |
115 | 28.6k | _flags = 0; |
116 | 28.6k | _mode = 0; |
117 | 28.6k | _name = nullptr; |
118 | 28.6k | _offset = 0; |
119 | 28.6k | _length = 0; |
120 | 28.6k | return ok; |
121 | 28.6k | } |
122 | | |
123 | 17.3k | void FileBase::closex() may_throw { |
124 | 17.3k | if (!close_noexcept()) |
125 | 0 | throwIOException("close failed", errno); |
126 | 17.3k | } |
127 | | |
128 | | // Return value of ::seek is the resulting file offset (same as ::tell()) |
129 | 342k | upx_off_t FileBase::seek(upx_off_t off, int whence) { |
130 | 342k | if (!isOpen()) |
131 | 0 | throwIOException("bad seek 1"); |
132 | 342k | if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check |
133 | 180 | throwIOException("bad seek"); |
134 | 342k | if (whence == SEEK_SET) { |
135 | 328k | if (off < 0) |
136 | 20 | throwIOException("bad seek 2"); |
137 | 328k | off += _offset; |
138 | 328k | } else if (whence == SEEK_END) { |
139 | 12.7k | if (off > 0) |
140 | 0 | throwIOException("bad seek 3"); |
141 | 12.7k | off += _offset + _length; |
142 | 12.7k | whence = SEEK_SET; |
143 | 12.7k | } else if (whence == SEEK_CUR) { |
144 | 715 | } else |
145 | 0 | throwInternalError("bad seek: whence"); |
146 | 342k | upx_off_t l = ::lseek(_fd, off, whence); |
147 | 342k | if (l < 0) |
148 | 0 | throwIOException("seek error", errno); |
149 | 342k | return l - _offset; |
150 | 342k | } |
151 | | |
152 | 111 | upx_off_t FileBase::tell() const { |
153 | 111 | if (!isOpen()) |
154 | 0 | throwIOException("bad tell"); |
155 | 111 | upx_off_t l = ::lseek(_fd, 0, SEEK_CUR); |
156 | 111 | if (l < 0) |
157 | 0 | throwIOException("tell error", errno); |
158 | 111 | return l - _offset; |
159 | 111 | } |
160 | | |
161 | 15 | void FileBase::set_extent(upx_off_t offset, upx_off_t length) { |
162 | 15 | _offset = offset; |
163 | 15 | _length = length; |
164 | 15 | } |
165 | | |
166 | 195k | upx_off_t FileBase::st_size() const { return _length; } |
167 | | |
168 | | /************************************************************************* |
169 | | // InputFile |
170 | | **************************************************************************/ |
171 | | |
172 | 5.70k | void InputFile::sopen(const char *name, int flags, int shflags) { |
173 | 5.70k | closex(); |
174 | 5.70k | _name = name; |
175 | 5.70k | _flags = flags; |
176 | 5.70k | _shflags = shflags; |
177 | 5.70k | _mode = 0; |
178 | 5.70k | _offset = 0; |
179 | 5.70k | _length = 0; |
180 | 5.70k | 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 | 5.70k | _length_orig = _length; |
189 | 5.70k | } |
190 | | |
191 | 204k | int InputFile::read(SPAN_P(void) buf, upx_int64_t blen) { |
192 | 204k | if (!isOpen() || blen < 0) |
193 | 0 | throwIOException("bad read"); |
194 | 204k | int len = (int) mem_size(1, blen); // sanity check |
195 | 204k | errno = 0; |
196 | 204k | long l = acc_safe_hread(_fd, raw_bytes(buf, len), len); |
197 | 204k | if (errno) |
198 | 0 | throwIOException("read error", errno); |
199 | 204k | return (int) l; |
200 | 204k | } |
201 | | |
202 | 201k | int InputFile::readx(SPAN_P(void) buf, upx_int64_t blen) { |
203 | 201k | int l = this->read(buf, blen); |
204 | 201k | if (l != blen) |
205 | 3.90k | throwEOFException(); |
206 | 197k | return l; |
207 | 201k | } |
208 | | |
209 | 342k | upx_off_t InputFile::seek(upx_off_t off, int whence) { |
210 | 342k | upx_off_t pos = super::seek(off, whence); |
211 | 342k | if (_length < pos) |
212 | 5.07k | throwIOException("bad seek 4"); |
213 | 337k | return pos; |
214 | 342k | } |
215 | | |
216 | 76 | upx_off_t InputFile::st_size_orig() const { return _length_orig; } |
217 | | |
218 | 0 | int InputFile::dupFd() may_throw { |
219 | 0 | if (!isOpen()) |
220 | 0 | throwIOException("bad dup"); |
221 | | #if defined(HAVE_DUP) && (HAVE_DUP + 0 == 0) |
222 | | errno = ENOSYS; |
223 | | int r = -1; |
224 | | #else |
225 | 0 | int r = ::dup(getFd()); |
226 | 0 | #endif |
227 | 0 | if (r < 0) |
228 | 0 | throwIOException("dup", errno); |
229 | 0 | return r; |
230 | 0 | } |
231 | | |
232 | | /************************************************************************* |
233 | | // OutputFile |
234 | | **************************************************************************/ |
235 | | |
236 | 0 | void OutputFile::sopen(const char *name, int flags, int shflags, int mode) { |
237 | 0 | closex(); |
238 | 0 | _name = name; |
239 | 0 | _flags = flags; |
240 | 0 | _shflags = shflags; |
241 | 0 | _mode = mode; |
242 | 0 | _offset = 0; |
243 | 0 | _length = 0; |
244 | 0 | if (!super::do_sopen()) { |
245 | | #if 0 |
246 | | // don't throw FileNotFound here -- this is confusing |
247 | | if (errno == ENOENT) |
248 | | throw FileNotFoundException(_name,errno); |
249 | | else |
250 | | #endif |
251 | 0 | if (errno == EEXIST) |
252 | 0 | throw FileAlreadyExistsException(_name, errno); |
253 | 0 | else |
254 | 0 | throwIOException(_name, errno); |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | 0 | bool OutputFile::openStdout(int flags, bool force) { |
259 | 0 | closex(); |
260 | 0 | int fd = STDOUT_FILENO; |
261 | 0 | if (!force && acc_isatty(fd)) |
262 | 0 | return false; |
263 | 0 | _name = "<stdout>"; |
264 | 0 | _flags = flags; |
265 | 0 | _shflags = -1; |
266 | 0 | _mode = 0; |
267 | 0 | _offset = 0; |
268 | 0 | _length = 0; |
269 | 0 | if (flags && acc_set_binmode(fd, 1) == -1) |
270 | 0 | throwIOException(_name, errno); |
271 | 0 | _fd = fd; |
272 | 0 | return true; |
273 | 0 | } |
274 | | |
275 | 0 | void OutputFile::write(SPAN_0(const void) buf, upx_int64_t blen) { |
276 | 0 | if (!isOpen() || blen < 0) |
277 | 0 | throwIOException("bad write"); |
278 | | // allow nullptr if blen == 0 |
279 | 0 | if (blen == 0) |
280 | 0 | return; |
281 | 0 | int len = (int) mem_size(1, blen); // sanity check |
282 | 0 | errno = 0; |
283 | 0 | #if WITH_XSPAN >= 2 |
284 | 0 | NO_fprintf(stderr, "write %p %zd (%p) %d\n", buf.raw_ptr(), buf.raw_size_in_bytes(), |
285 | 0 | buf.raw_base(), len); |
286 | 0 | #endif |
287 | 0 | long l = acc_safe_hwrite(_fd, raw_bytes(buf, len), len); |
288 | 0 | if (l != len) |
289 | 0 | throwIOException("write error", errno); |
290 | 0 | bytes_written += len; |
291 | | #if TESTING && 0 |
292 | | static upx_std_atomic(bool) dumping; |
293 | | if (!dumping) { |
294 | | dumping = true; |
295 | | char fn[64]; |
296 | | static int part = 0; |
297 | | snprintf(fn, sizeof(fn), "upx-dump-%04d.data", part++); |
298 | | OutputFile::dump(fn, buf, len); |
299 | | dumping = false; |
300 | | } |
301 | | #endif |
302 | 0 | } |
303 | | |
304 | 0 | upx_off_t OutputFile::st_size() const { |
305 | 0 | if (opt->to_stdout) { // might be a pipe ==> .st_size is invalid |
306 | 0 | return bytes_written; // too big if seek()+write() instead of rewrite() |
307 | 0 | } |
308 | 0 | struct stat my_st; |
309 | 0 | my_st.st_size = 0; |
310 | 0 | if (::fstat(_fd, &my_st) != 0) |
311 | 0 | throwIOException(_name, errno); |
312 | 0 | return my_st.st_size; |
313 | 0 | } |
314 | | |
315 | 0 | void OutputFile::rewrite(SPAN_P(const void) buf, int len) { |
316 | 0 | assert(!opt->to_stdout); |
317 | 0 | write(buf, len); |
318 | 0 | bytes_written -= len; // restore |
319 | 0 | } |
320 | | |
321 | 0 | upx_off_t OutputFile::seek(upx_off_t off, int whence) { |
322 | 0 | if (!mem_size_valid_bytes(off >= 0 ? off : -off)) // sanity check |
323 | 0 | throwIOException("bad seek"); |
324 | 0 | assert(!opt->to_stdout); |
325 | 0 | switch (whence) { |
326 | 0 | case SEEK_SET: |
327 | 0 | if (bytes_written < off) |
328 | 0 | bytes_written = off; |
329 | 0 | _length = bytes_written; // cheap, lazy update; needed? |
330 | 0 | break; |
331 | 0 | case SEEK_END: |
332 | 0 | _length = bytes_written; // necessary |
333 | 0 | break; |
334 | 0 | } |
335 | 0 | return super::seek(off, whence); |
336 | 0 | } |
337 | | |
338 | | // WARNING: fsync() does not exist in some Windows environments. |
339 | | // This trick works only on UNIX-like systems. |
340 | | // int OutputFile::read(void *buf, int len) { |
341 | | // fsync(_fd); |
342 | | // InputFile infile; |
343 | | // infile.open(this->getName(), O_RDONLY | O_BINARY); |
344 | | // infile.seek(this->tell(), SEEK_SET); |
345 | | // return infile.read(buf, len); |
346 | | //} |
347 | | |
348 | 0 | void OutputFile::set_extent(upx_off_t offset, upx_off_t length) { |
349 | 0 | super::set_extent(offset, length); |
350 | 0 | bytes_written = 0; |
351 | 0 | if (0 == offset && 0xffffffffLL == length) { // TODO: check all callers of this method |
352 | 0 | st.st_size = 0; |
353 | 0 | if (::fstat(_fd, &st) != 0) |
354 | 0 | throwIOException(_name, errno); |
355 | 0 | _length = st.st_size - offset; |
356 | 0 | } |
357 | 0 | } |
358 | | |
359 | 0 | upx_off_t OutputFile::unset_extent() { |
360 | 0 | upx_off_t l = ::lseek(_fd, 0, SEEK_END); |
361 | 0 | if (l < 0) |
362 | 0 | throwIOException("lseek error", errno); |
363 | 0 | _offset = 0; |
364 | 0 | _length = l; |
365 | 0 | bytes_written = _length; |
366 | 0 | return _length; |
367 | 0 | } |
368 | | |
369 | 0 | /*static*/ void OutputFile::dump(const char *name, SPAN_P(const void) buf, int len, int flags) { |
370 | 0 | if (flags < 0) |
371 | 0 | flags = O_CREAT | O_TRUNC; |
372 | 0 | flags |= O_WRONLY | O_BINARY; |
373 | 0 | OutputFile f; |
374 | 0 | f.open(name, flags, 0600); |
375 | 0 | f.write(raw_bytes(buf, len), len); |
376 | 0 | f.closex(); |
377 | 0 | } |
378 | | |
379 | | /************************************************************************* |
380 | | // |
381 | | **************************************************************************/ |
382 | | |
383 | 5.71k | TEST_CASE("file") { |
384 | 5.71k | InputFile fi; |
385 | 5.71k | CHECK(!fi.isOpen()); |
386 | 5.71k | CHECK(fi.getFd() == -1); |
387 | 5.71k | CHECK(fi.st_size() == 0); |
388 | 5.71k | OutputFile fo; |
389 | 5.71k | CHECK(!fo.isOpen()); |
390 | 5.71k | CHECK(fo.getFd() == -1); |
391 | 5.71k | CHECK(fo.getBytesWritten() == 0); |
392 | 5.71k | } |
393 | | |
394 | | /* vim:set ts=4 sw=4 et: */ |