Commit 5993c3e83c6f83b36045c75a03ffb1da3d1d283c

Authored by Jay Berkenbilt
1 parent 885b8781

Detect input file = output file (fixes #29)

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
... ... @@ -284,3 +284,4 @@ QPDFWriter preserve unreferenced standard 0
284 284 QPDFObjectHandle non-stream in parsecontent 0
285 285 QPDFObjectHandle errors in parsecontent 0
286 286 QPDF stream with non-space 0
  287 +qpdf same file error 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -206,7 +206,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;,
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-&gt;runtest(&quot;dump corrected bad xref&quot;,
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 # ----------
... ...