Commit 5993c3e83c6f83b36045c75a03ffb1da3d1d283c
1 parent
885b8781
Detect input file = output file (fixes #29)
Showing
9 changed files
with
108 additions
and
1 deletions
ChangeLog
| 1 | 2017-07-29 Jay Berkenbilt <ejb@ql.org> | 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 | * When passing multiple inspection arguments, run --check first, | 6 | * When passing multiple inspection arguments, run --check first, |
| 4 | and defer exit until after all the checks have been run. This | 7 | and defer exit until after all the checks have been run. This |
| 5 | makes it possible to force operations such as --show-xref to be | 8 | makes it possible to force operations such as --show-xref to be |
include/qpdf/QUtil.hh
| @@ -75,6 +75,9 @@ namespace QUtil | @@ -75,6 +75,9 @@ namespace QUtil | ||
| 75 | qpdf_offset_t tell(FILE* stream); | 75 | qpdf_offset_t tell(FILE* stream); |
| 76 | 76 | ||
| 77 | QPDF_DLL | 77 | QPDF_DLL |
| 78 | + bool same_file(char const* name1, char const* name2); | ||
| 79 | + | ||
| 80 | + QPDF_DLL | ||
| 78 | char* copy_string(std::string const&); | 81 | char* copy_string(std::string const&); |
| 79 | 82 | ||
| 80 | // Returns lower-case hex-encoded version of the string, treating | 83 | // Returns lower-case hex-encoded version of the string, treating |
libqpdf/QUtil.cc
| @@ -24,6 +24,7 @@ | @@ -24,6 +24,7 @@ | ||
| 24 | #include <io.h> | 24 | #include <io.h> |
| 25 | #else | 25 | #else |
| 26 | #include <unistd.h> | 26 | #include <unistd.h> |
| 27 | +#include <sys/stat.h> | ||
| 27 | #endif | 28 | #endif |
| 28 | 29 | ||
| 29 | std::string | 30 | std::string |
| @@ -188,6 +189,55 @@ QUtil::tell(FILE* stream) | @@ -188,6 +189,55 @@ QUtil::tell(FILE* stream) | ||
| 188 | #endif | 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 | char* | 241 | char* |
| 192 | QUtil::copy_string(std::string const& str) | 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,3 +35,10 @@ quack1 | ||
| 35 | quack2 | 35 | quack2 |
| 36 | quack3 | 36 | quack3 |
| 37 | quack4 | 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,6 +140,36 @@ void get_whoami_test() | ||
| 140 | print_whoami("a\\b\\c\\quack4.exe"); | 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 | int main(int argc, char* argv[]) | 173 | int main(int argc, char* argv[]) |
| 144 | { | 174 | { |
| 145 | try | 175 | try |
| @@ -155,6 +185,8 @@ int main(int argc, char* argv[]) | @@ -155,6 +185,8 @@ int main(int argc, char* argv[]) | ||
| 155 | to_utf8_test(); | 185 | to_utf8_test(); |
| 156 | std::cout << "----" << std::endl; | 186 | std::cout << "----" << std::endl; |
| 157 | get_whoami_test(); | 187 | get_whoami_test(); |
| 188 | + std::cout << "----" << std::endl; | ||
| 189 | + same_file_test(); | ||
| 158 | } | 190 | } |
| 159 | catch (std::exception& e) | 191 | catch (std::exception& e) |
| 160 | { | 192 | { |
qpdf/qpdf.cc
| @@ -1361,6 +1361,12 @@ int main(int argc, char* argv[]) | @@ -1361,6 +1361,12 @@ int main(int argc, char* argv[]) | ||
| 1361 | usage("no output file may be given for this option"); | 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 | try | 1370 | try |
| 1365 | { | 1371 | { |
| 1366 | QPDF pdf; | 1372 | QPDF pdf; |
qpdf/qpdf.testcov
| @@ -284,3 +284,4 @@ QPDFWriter preserve unreferenced standard 0 | @@ -284,3 +284,4 @@ QPDFWriter preserve unreferenced standard 0 | ||
| 284 | QPDFObjectHandle non-stream in parsecontent 0 | 284 | QPDFObjectHandle non-stream in parsecontent 0 |
| 285 | QPDFObjectHandle errors in parsecontent 0 | 285 | QPDFObjectHandle errors in parsecontent 0 |
| 286 | QPDF stream with non-space 0 | 286 | QPDF stream with non-space 0 |
| 287 | +qpdf same file error 0 |
qpdf/qtest/qpdf.test
| @@ -206,7 +206,7 @@ $td->runtest("remove page we don't have", | @@ -206,7 +206,7 @@ $td->runtest("remove page we don't have", | ||
| 206 | show_ntests(); | 206 | show_ntests(); |
| 207 | # ---------- | 207 | # ---------- |
| 208 | $td->notify("--- Miscellaneous Tests ---"); | 208 | $td->notify("--- Miscellaneous Tests ---"); |
| 209 | -$n_tests += 93; | 209 | +$n_tests += 94; |
| 210 | 210 | ||
| 211 | $td->runtest("qpdf version", | 211 | $td->runtest("qpdf version", |
| 212 | {$td->COMMAND => "qpdf --version"}, | 212 | {$td->COMMAND => "qpdf --version"}, |
| @@ -641,6 +641,10 @@ $td->runtest("dump corrected bad xref", | @@ -641,6 +641,10 @@ $td->runtest("dump corrected bad xref", | ||
| 641 | $td->EXIT_STATUS => 3}, | 641 | $td->EXIT_STATUS => 3}, |
| 642 | $td->NORMALIZE_NEWLINES); | 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 | show_ntests(); | 649 | show_ntests(); |
| 646 | # ---------- | 650 | # ---------- |