aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--include/qpdf/QUtil.hh3
-rw-r--r--libqpdf/QUtil.cc50
-rw-r--r--libtests/qtest/qutil/other-file1
-rw-r--r--libtests/qtest/qutil/qutil.out7
-rw-r--r--libtests/qutil.cc32
-rw-r--r--qpdf/qpdf.cc6
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test6
9 files changed, 108 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index ac32ea25..8d1cf56a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
2017-07-29 Jay Berkenbilt <ejb@ql.org>
+ * Detect when input file and output file are the same and exit to
+ avoid overwriting and losing input file. Fixes #29.
+
* When passing multiple inspection arguments, run --check first,
and defer exit until after all the checks have been run. This
makes it possible to force operations such as --show-xref to be
diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh
index eba4ef61..98784a37 100644
--- a/include/qpdf/QUtil.hh
+++ b/include/qpdf/QUtil.hh
@@ -75,6 +75,9 @@ namespace QUtil
qpdf_offset_t tell(FILE* stream);
QPDF_DLL
+ bool same_file(char const* name1, char const* name2);
+
+ QPDF_DLL
char* copy_string(std::string const&);
// Returns lower-case hex-encoded version of the string, treating
diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc
index 86de07f2..eed8d276 100644
--- a/libqpdf/QUtil.cc
+++ b/libqpdf/QUtil.cc
@@ -24,6 +24,7 @@
#include <io.h>
#else
#include <unistd.h>
+#include <sys/stat.h>
#endif
std::string
@@ -188,6 +189,55 @@ QUtil::tell(FILE* stream)
#endif
}
+bool
+QUtil::same_file(char const* name1, char const* name2)
+{
+ if ((name1 == 0) || (strlen(name1) == 0) ||
+ (name2 == 0) || (strlen(name2) == 0))
+ {
+ return false;
+ }
+#ifdef _WIN32
+ HANDLE fh1 = CreateFile(name1, GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ HANDLE fh2 = CreateFile(name2, GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ BY_HANDLE_FILE_INFORMATION fi1;
+ BY_HANDLE_FILE_INFORMATION fi2;
+ bool same = false;
+ if ((fh1 != INVALID_HANDLE_VALUE) &&
+ (fh2 != INVALID_HANDLE_VALUE) &&
+ GetFileInformationByHandle(fh1, &fi1) &&
+ GetFileInformationByHandle(fh2, &fi2) &&
+ (fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber) &&
+ (fi1.nFileIndexLow == fi2.nFileIndexLow) &&
+ (fi1.nFileIndexHigh == fi2.nFileIndexHigh))
+ {
+ same = true;
+ }
+ if (fh1 != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(fh1);
+ }
+ if (fh2 != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(fh2);
+ }
+ return same;
+#else
+ struct stat st1;
+ struct stat st2;
+ if ((stat(name1, &st1) == 0) &&
+ (stat(name2, &st2) == 0) &&
+ (st1.st_ino == st2.st_ino) &&
+ (st1.st_dev == st2.st_dev))
+ {
+ return true;
+ }
+#endif
+ return false;
+}
+
char*
QUtil::copy_string(std::string const& str)
{
diff --git a/libtests/qtest/qutil/other-file b/libtests/qtest/qutil/other-file
new file mode 100644
index 00000000..9daeafb9
--- /dev/null
+++ b/libtests/qtest/qutil/other-file
@@ -0,0 +1 @@
+test
diff --git a/libtests/qtest/qutil/qutil.out b/libtests/qtest/qutil/qutil.out
index 4273fe11..453185d4 100644
--- a/libtests/qtest/qutil/qutil.out
+++ b/libtests/qtest/qutil/qutil.out
@@ -35,3 +35,10 @@ quack1
quack2
quack3
quack4
+----
+file1: -qutil.out-, file2: -./qutil.out-; same: 1: PASS
+file1: -qutil.out-, file2: -qutil.out-; same: 1: PASS
+file1: -qutil.out-, file2: -other-file-; same: 0: PASS
+file1: -qutil.out-, file2: --; same: 0: PASS
+file1: -qutil.out-, file2: -(null)-; same: 0: PASS
+file1: --, file2: -qutil.out-; same: 0: PASS
diff --git a/libtests/qutil.cc b/libtests/qutil.cc
index b0134e79..5e562b89 100644
--- a/libtests/qutil.cc
+++ b/libtests/qutil.cc
@@ -140,6 +140,36 @@ void get_whoami_test()
print_whoami("a\\b\\c\\quack4.exe");
}
+void assert_same_file(char const* file1, char const* file2, bool expected)
+{
+ bool actual = QUtil::same_file(file1, file2);
+ std::cout << "file1: -" << (file1 ? file1 : "(null)") << "-, file2: -"
+ << (file2 ? file2 : "(null)") << "-; same: "
+ << actual << ": " << ((actual == expected) ? "PASS" : "FAIL")
+ << std::endl;
+}
+
+void same_file_test()
+{
+ try
+ {
+ fclose(QUtil::safe_fopen("qutil.out", "r"));
+ fclose(QUtil::safe_fopen("other-file", "r"));
+ }
+ catch (std::exception)
+ {
+ std::cout << "same_file_test expects to have qutil.out and other-file"
+ " exist in the current directory\n";
+ return;
+ }
+ assert_same_file("qutil.out", "./qutil.out", true);
+ assert_same_file("qutil.out", "qutil.out", true);
+ assert_same_file("qutil.out", "other-file", false);
+ assert_same_file("qutil.out", "", false);
+ assert_same_file("qutil.out", 0, false);
+ assert_same_file("", "qutil.out", false);
+}
+
int main(int argc, char* argv[])
{
try
@@ -155,6 +185,8 @@ int main(int argc, char* argv[])
to_utf8_test();
std::cout << "----" << std::endl;
get_whoami_test();
+ std::cout << "----" << std::endl;
+ same_file_test();
}
catch (std::exception& e)
{
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index ae365c70..a1388692 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -1361,6 +1361,12 @@ int main(int argc, char* argv[])
usage("no output file may be given for this option");
}
+ if (QUtil::same_file(infilename, outfilename))
+ {
+ QTC::TC("qpdf", "qpdf same file error");
+ usage("input file and output file are the same; this would cause input file to be lost");
+ }
+
try
{
QPDF pdf;
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index d1ddd55d..5f810b03 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -284,3 +284,4 @@ QPDFWriter preserve unreferenced standard 0
QPDFObjectHandle non-stream in parsecontent 0
QPDFObjectHandle errors in parsecontent 0
QPDF stream with non-space 0
+qpdf same file error 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 031c33a9..f0205e1d 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -206,7 +206,7 @@ $td->runtest("remove page we don't have",
show_ntests();
# ----------
$td->notify("--- Miscellaneous Tests ---");
-$n_tests += 93;
+$n_tests += 94;
$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@@ -641,6 +641,10 @@ $td->runtest("dump corrected bad xref",
$td->EXIT_STATUS => 3},
$td->NORMALIZE_NEWLINES);
+$td->runtest("don't overwrite self",
+ {$td->COMMAND => "qpdf a.pdf a.pdf"},
+ {$td->REGEXP => "input file and output file are the same.*",
+ $td->EXIT_STATUS => 2});
show_ntests();
# ----------