0

I have some .exe file (say some.exe) that writes to the standard output binary data. I have no sources of this program. I need to run some.exe from my C++/Qt application and read standard output of the process I created. When I'm trying to do this with QProcess::readAll someone replaces byte \n (0x0d) to \r\n (0x0a 0x0d).

Here is a code:

QProcess some;
some.start( "some.exe", QStringList() << "-I" << "inp.txt" );
// some.setTextModeEnabled( false ); // no effect at all
some.waitForFinished();
QByteArray output = some.readAll();

I tried in cmd.exe to redirect output to file like this:

some.exe -I inp.txt > out.bin

and viewed out.bin with hexedit there was 0a 0d in the place where should be 0d.

Edit:
Here is a simple program to emulate some.exe behaviour:

#include <stdio.h>
int main() {
    char buf[] = { 0x00, 0x11, 0x0a, 0x33 };
    fwrite( buf, sizeof( buf[ 0 ] ), sizeof( buf ), stdout );
}

run:

a.exe > out.bin

//out.bin
00 11 0d 0a 33

Note, that I can't modify some.exe that's why I shouldn't modify my example like _setmode( _fileno( stdout, BINARY ) )

The question is: how can I say to QProcess or to Windows or to console do not change CR with LF CR?

OS: Windows 7
Qt: 5.6.2

borisbn
  • 4,988
  • 25
  • 42
  • You want to modify the output of a program that you do not drive, do you think that question is valid? – eyllanesc Jul 25 '18 at 14:51
  • Sorry, I can't reproduce it. Perhaps add `Q_ASSERT(!(some.openMode() & QIODevice::Text))`. You'll need to provide an example. Ideally put both the source of the data and the reader in the same executable, and have the reader start the writer. The discrimination which mode to run in can be based on command line arguments. Put it all into a single file. – Kuba hasn't forgotten Monica Jul 25 '18 at 16:13
  • How do you know that `some.exe` is not outputting the data you claim? You could redirect it to a file and then look at it in a hex editor to confirm. – Kuba hasn't forgotten Monica Jul 25 '18 at 16:16
  • Have you enabled text mode? – Devopia Jul 26 '18 at 03:18
  • Sorry for late. I do not want to modify the output. I want Windows do not modify it. I'll edit a question and add an example of some.exe. I don't know how to eneble text mode, but I need to disable it. – borisbn Jul 26 '18 at 08:29

2 Answers2

1

Unfortunately it has nothing to do with QProcess or Windows or console. It's all about CRT. Functions like printf or fwrite are taking into account _O_TEXT flag to add an additional 0x0D (true only for Windows). So the only solution is to modify stdout's fields of your some.exe with WriteProcessMemory or call the _setmode inside an address space of your some.exe with DLL Injection technique or patch the lib. But it's a tricky job.

Dmitrii
  • 509
  • 4
  • 2
1

how can I say to QProcess or to Windows or to console do not change CR with LF CR?

They don't change anything. some.exe is broken. That's all. It outputs the wrong thing. Whoever made it output brinary data in text mode has messed up badly.

There's a way to recover, though. You have to implement a decoder that will fix the broken output of some.exe. You know that every 0a has to be preceded by 0d. So you have to parse the output, and if you find a 0a, and there's 0d before it, remove the 0d, and continue. Optionally, you can abort if a 0a is not preceded by 0d - some.exe should not produce such output since it's broken.

The appendBinFix function takes the corrupted data and appends the fixed version to a buffer.

// https://github.com/KubaO/stackoverflown/tree/master/questions/process-fix-binary-crlf-51519654
#include <QtCore>
#include <algorithm>

bool appendBinFix(QByteArray &buf, const char *src, int size) {
   bool okData = true;
   if (!size) return okData;
   constexpr char CR = '\x0d';
   constexpr char LF = '\x0a';
   bool hasCR = buf.endsWith(CR);
   buf.resize(buf.size() + size);
   char *dst = buf.end() - size;
   const char *lastSrc = src;
   for (const char *const end = src + size; src != end; src++) {
      char const c = *src;
      if (c == LF) {
         if (hasCR) {
            std::copy(lastSrc, src, dst);
            dst += (src - lastSrc);
            dst[-1] = LF;
            lastSrc = src + 1;
         } else
            okData = false;
      }
      hasCR = (c == CR);
   }
   dst = std::copy(lastSrc, src, dst);
   buf.resize(dst - buf.constData());
   return okData;
}

bool appendBinFix(QByteArray &buf, const QByteArray &src) {
   return appendBinFix(buf, src.data(), src.size());
}

The following test harness ensures that it does the right thing, including emulating the output of some.exe (itself):

#include <QtTest>
#include <cstdio>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#endif

const auto dataFixed = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
const auto data = QByteArrayLiteral("\x00\x11\x0d\x0d\x0a\x33");

int writeOutput() {
#ifdef Q_OS_WIN
   _setmode(_fileno(stdout), _O_BINARY);
#endif
   auto size = fwrite(data.data(), 1, data.size(), stdout);
   qDebug() << size << data.size();
   return (size == data.size()) ? 0 : 1;
}

class AppendTest : public QObject {
   Q_OBJECT
   struct Result {
      QByteArray d;
      bool ok;
      bool operator==(const Result &o) const { return ok == o.ok && d == o.d; }
   };
   static Result getFixed(const QByteArray &src, int split) {
      Result f;
      f.ok = appendBinFix(f.d, src.data(), split);
      f.ok = appendBinFix(f.d, src.data() + split, src.size() - split) && f.ok;
      return f;
   }
   Q_SLOT void worksWithLFCR() {
      const auto lf_cr = QByteArrayLiteral("\x00\x11\x0a\x0d\x33");
      for (int i = 0; i < lf_cr.size(); ++i)
         QCOMPARE(getFixed(lf_cr, i), (Result{lf_cr, false}));
   }
   Q_SLOT void worksWithCRLF() {
      const auto cr_lf = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
      const auto cr_lf_fixed = QByteArrayLiteral("\x00\x11\x0a\x33");
      for (int i = 0; i < cr_lf.size(); ++i)
         QCOMPARE(getFixed(cr_lf, i), (Result{cr_lf_fixed, true}));
   }
   Q_SLOT void worksWithCRCRLF() {
      for (int i = 0; i < data.size(); ++i) QCOMPARE(getFixed(data, i).d, dataFixed);
   }
   Q_SLOT void worksWithQProcess() {
      QProcess proc;
      proc.start(QCoreApplication::applicationFilePath(), {"output"},
                 QIODevice::ReadOnly);
      proc.waitForFinished(5000);
      QCOMPARE(proc.exitCode(), 0);
      QCOMPARE(proc.exitStatus(), QProcess::NormalExit);

      QByteArray out = proc.readAllStandardOutput();
      QByteArray fixed;
      appendBinFix(fixed, out);
      QCOMPARE(out, data);
      QCOMPARE(fixed, dataFixed);
   }
};

int main(int argc, char *argv[]) {
   QCoreApplication app(argc, argv);
   if (app.arguments().size() > 1) return writeOutput();
   AppendTest test;
   QTEST_SET_MAIN_SOURCE_PATH
   return QTest::qExec(&test, argc, argv);
}
#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thank you for solution and code. I made the same workaround - `buf..replace( "\r\n", "\n" );` - but I thought that there's less *hacked* way)) – borisbn Jul 30 '18 at 06:04
  • @borisbn It can't be any less hacked because Qt is not doing anything wrong, nor is such a conversion usual. The source of the data is clearly malfunctioning. – Kuba hasn't forgotten Monica Jul 30 '18 at 16:02