Commit 5993c3e83c6f83b36045c75a03ffb1da3d1d283c
1 parent
885b8781
Detect input file = output file (fixes #29)
Showing
9 changed files
with
108 additions
and
1 deletions
ChangeLog
| 1 | 1 | 2017-07-29 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * Detect when input file and output file are the same and exit to | |
| 4 | + avoid overwriting and losing input file. Fixes #29. | |
| 5 | + | |
| 3 | 6 | * When passing multiple inspection arguments, run --check first, |
| 4 | 7 | and defer exit until after all the checks have been run. This |
| 5 | 8 | makes it possible to force operations such as --show-xref to be | ... | ... |
include/qpdf/QUtil.hh
| ... | ... | @@ -75,6 +75,9 @@ namespace QUtil |
| 75 | 75 | qpdf_offset_t tell(FILE* stream); |
| 76 | 76 | |
| 77 | 77 | QPDF_DLL |
| 78 | + bool same_file(char const* name1, char const* name2); | |
| 79 | + | |
| 80 | + QPDF_DLL | |
| 78 | 81 | char* copy_string(std::string const&); |
| 79 | 82 | |
| 80 | 83 | // Returns lower-case hex-encoded version of the string, treating | ... | ... |
libqpdf/QUtil.cc
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | #include <io.h> |
| 25 | 25 | #else |
| 26 | 26 | #include <unistd.h> |
| 27 | +#include <sys/stat.h> | |
| 27 | 28 | #endif |
| 28 | 29 | |
| 29 | 30 | std::string |
| ... | ... | @@ -188,6 +189,55 @@ QUtil::tell(FILE* stream) |
| 188 | 189 | #endif |
| 189 | 190 | } |
| 190 | 191 | |
| 192 | +bool | |
| 193 | +QUtil::same_file(char const* name1, char const* name2) | |
| 194 | +{ | |
| 195 | + if ((name1 == 0) || (strlen(name1) == 0) || | |
| 196 | + (name2 == 0) || (strlen(name2) == 0)) | |
| 197 | + { | |
| 198 | + return false; | |
| 199 | + } | |
| 200 | +#ifdef _WIN32 | |
| 201 | + HANDLE fh1 = CreateFile(name1, GENERIC_READ, FILE_SHARE_READ, | |
| 202 | + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
| 203 | + HANDLE fh2 = CreateFile(name2, GENERIC_READ, FILE_SHARE_READ, | |
| 204 | + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
| 205 | + BY_HANDLE_FILE_INFORMATION fi1; | |
| 206 | + BY_HANDLE_FILE_INFORMATION fi2; | |
| 207 | + bool same = false; | |
| 208 | + if ((fh1 != INVALID_HANDLE_VALUE) && | |
| 209 | + (fh2 != INVALID_HANDLE_VALUE) && | |
| 210 | + GetFileInformationByHandle(fh1, &fi1) && | |
| 211 | + GetFileInformationByHandle(fh2, &fi2) && | |
| 212 | + (fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber) && | |
| 213 | + (fi1.nFileIndexLow == fi2.nFileIndexLow) && | |
| 214 | + (fi1.nFileIndexHigh == fi2.nFileIndexHigh)) | |
| 215 | + { | |
| 216 | + same = true; | |
| 217 | + } | |
| 218 | + if (fh1 != INVALID_HANDLE_VALUE) | |
| 219 | + { | |
| 220 | + CloseHandle(fh1); | |
| 221 | + } | |
| 222 | + if (fh2 != INVALID_HANDLE_VALUE) | |
| 223 | + { | |
| 224 | + CloseHandle(fh2); | |
| 225 | + } | |
| 226 | + return same; | |
| 227 | +#else | |
| 228 | + struct stat st1; | |
| 229 | + struct stat st2; | |
| 230 | + if ((stat(name1, &st1) == 0) && | |
| 231 | + (stat(name2, &st2) == 0) && | |
| 232 | + (st1.st_ino == st2.st_ino) && | |
| 233 | + (st1.st_dev == st2.st_dev)) | |
| 234 | + { | |
| 235 | + return true; | |
| 236 | + } | |
| 237 | +#endif | |
| 238 | + return false; | |
| 239 | +} | |
| 240 | + | |
| 191 | 241 | char* |
| 192 | 242 | QUtil::copy_string(std::string const& str) |
| 193 | 243 | { | ... | ... |
libtests/qtest/qutil/other-file
0 → 100644
| 1 | +test | ... | ... |
libtests/qtest/qutil/qutil.out
| ... | ... | @@ -35,3 +35,10 @@ quack1 |
| 35 | 35 | quack2 |
| 36 | 36 | quack3 |
| 37 | 37 | quack4 |
| 38 | +---- | |
| 39 | +file1: -qutil.out-, file2: -./qutil.out-; same: 1: PASS | |
| 40 | +file1: -qutil.out-, file2: -qutil.out-; same: 1: PASS | |
| 41 | +file1: -qutil.out-, file2: -other-file-; same: 0: PASS | |
| 42 | +file1: -qutil.out-, file2: --; same: 0: PASS | |
| 43 | +file1: -qutil.out-, file2: -(null)-; same: 0: PASS | |
| 44 | +file1: --, file2: -qutil.out-; same: 0: PASS | ... | ... |
libtests/qutil.cc
| ... | ... | @@ -140,6 +140,36 @@ void get_whoami_test() |
| 140 | 140 | print_whoami("a\\b\\c\\quack4.exe"); |
| 141 | 141 | } |
| 142 | 142 | |
| 143 | +void assert_same_file(char const* file1, char const* file2, bool expected) | |
| 144 | +{ | |
| 145 | + bool actual = QUtil::same_file(file1, file2); | |
| 146 | + std::cout << "file1: -" << (file1 ? file1 : "(null)") << "-, file2: -" | |
| 147 | + << (file2 ? file2 : "(null)") << "-; same: " | |
| 148 | + << actual << ": " << ((actual == expected) ? "PASS" : "FAIL") | |
| 149 | + << std::endl; | |
| 150 | +} | |
| 151 | + | |
| 152 | +void same_file_test() | |
| 153 | +{ | |
| 154 | + try | |
| 155 | + { | |
| 156 | + fclose(QUtil::safe_fopen("qutil.out", "r")); | |
| 157 | + fclose(QUtil::safe_fopen("other-file", "r")); | |
| 158 | + } | |
| 159 | + catch (std::exception) | |
| 160 | + { | |
| 161 | + std::cout << "same_file_test expects to have qutil.out and other-file" | |
| 162 | + " exist in the current directory\n"; | |
| 163 | + return; | |
| 164 | + } | |
| 165 | + assert_same_file("qutil.out", "./qutil.out", true); | |
| 166 | + assert_same_file("qutil.out", "qutil.out", true); | |
| 167 | + assert_same_file("qutil.out", "other-file", false); | |
| 168 | + assert_same_file("qutil.out", "", false); | |
| 169 | + assert_same_file("qutil.out", 0, false); | |
| 170 | + assert_same_file("", "qutil.out", false); | |
| 171 | +} | |
| 172 | + | |
| 143 | 173 | int main(int argc, char* argv[]) |
| 144 | 174 | { |
| 145 | 175 | try |
| ... | ... | @@ -155,6 +185,8 @@ int main(int argc, char* argv[]) |
| 155 | 185 | to_utf8_test(); |
| 156 | 186 | std::cout << "----" << std::endl; |
| 157 | 187 | get_whoami_test(); |
| 188 | + std::cout << "----" << std::endl; | |
| 189 | + same_file_test(); | |
| 158 | 190 | } |
| 159 | 191 | catch (std::exception& e) |
| 160 | 192 | { | ... | ... |
qpdf/qpdf.cc
| ... | ... | @@ -1361,6 +1361,12 @@ int main(int argc, char* argv[]) |
| 1361 | 1361 | usage("no output file may be given for this option"); |
| 1362 | 1362 | } |
| 1363 | 1363 | |
| 1364 | + if (QUtil::same_file(infilename, outfilename)) | |
| 1365 | + { | |
| 1366 | + QTC::TC("qpdf", "qpdf same file error"); | |
| 1367 | + usage("input file and output file are the same; this would cause input file to be lost"); | |
| 1368 | + } | |
| 1369 | + | |
| 1364 | 1370 | try |
| 1365 | 1371 | { |
| 1366 | 1372 | QPDF pdf; | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -206,7 +206,7 @@ $td->runtest("remove page we don't have", |
| 206 | 206 | show_ntests(); |
| 207 | 207 | # ---------- |
| 208 | 208 | $td->notify("--- Miscellaneous Tests ---"); |
| 209 | -$n_tests += 93; | |
| 209 | +$n_tests += 94; | |
| 210 | 210 | |
| 211 | 211 | $td->runtest("qpdf version", |
| 212 | 212 | {$td->COMMAND => "qpdf --version"}, |
| ... | ... | @@ -641,6 +641,10 @@ $td->runtest("dump corrected bad xref", |
| 641 | 641 | $td->EXIT_STATUS => 3}, |
| 642 | 642 | $td->NORMALIZE_NEWLINES); |
| 643 | 643 | |
| 644 | +$td->runtest("don't overwrite self", | |
| 645 | + {$td->COMMAND => "qpdf a.pdf a.pdf"}, | |
| 646 | + {$td->REGEXP => "input file and output file are the same.*", | |
| 647 | + $td->EXIT_STATUS => 2}); | |
| 644 | 648 | |
| 645 | 649 | show_ntests(); |
| 646 | 650 | # ---------- | ... | ... |