Cannot write across a pipe with pointers to istream and ostream

First some background:

// BEGIN BACKGROUND
I am running under CentOS 6.5 Linux on a x86_64, and compiling with c++ (GCC) 4.4.7 20120313.

I have a couple utility programs. One reads a message with a constant size on its stdin, converts it to a slightly different message, and writes that to its stdout. The other reads the slightly different message on its stdin, and sends it to a port. I want to execute a command of the form `cat file | util1 | util2` to convert and send messages stored in the file.

I originally implemented them both using std::cout.write and std::cin.read, and it worked fine. However, I want to make use of a class that can do neat things with the messages and makes my utilities' code cleaner. So, that class takes a name string and an istream* or ostream* and sticks them in a std::map<std::string,std::istream>, or a std::map<std::string,std::ostream> for use later. Naturally, it's a pointer to std::cin or a pointer to std::cout that I'm putting in the maps.

Then, in util1, I tell it to send a message to the named stream, and it does, basically, the following (yes, I use parens that are unnecessary, because I'm paranoid):

// BEGIN SNIP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
std::ostream *o_file = (o_file_map[name_string]);
uint32_t      sync   = fromSomewhere();
uint32_t      msgLen = sizeof(msg);

size_t        outPos = o_file->tellp();
size_t        nxtPos = o_file->write((char*)&sync,sizeof(sync)).tellp();
bool          result = ((nxtPos - outPos) == sizeof(sync));

if (result) {
    outPos = o_file->tellp();
    nxtPos = o_file->write((char*)&msgLen,sizeof(msgLen)).tellp();
    result = ((nxtPos - outPos) == sizeof(msgLen));
} else {
    std::err << "COMPLAINT1!" << std::endl;
}

if (result) {
    outPos = o_file->tellp();
    nxtPos = o_file->write((char*)&msg,msgLen).tellp();
    result = ((nxtPos - outPos) == msgLen);
} else {
    std::err << "COMPLAINT2!" << std::endl;
}

if (!result) {
    std::err << "COMPLAINT3!" << std::endl;
}

o_file->flush();

// END SNIP


In util2, I tell it to look for a message, and it does, essentially, this:

// BEGIN SNIP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
std::istream *i_file  = (i_file_map[name_string]);
uint32_t      tstSync = 0;
uint32_t      msgSize = 0; // Yeah, I called it msgLen before.
bool          ok      = true;
bool          do_flip = false;
size_t        readLen = 0;

if (*i_file) {
   readLen = i_file->read((char*)&tstSync, sizeof(tstSync)).gcount();
   ok = (readLen / sizeof(tstSync) > 0);
}

if (ok && (*i_file)) {
   readLen = i_file->read((char*)&msgSize, sizeof(msgSize)).gcount();
   ok = (readLen / sizeof(msgSize) > 0);
}

if (ok) {
   if (!isSync(tstSync)) {
      if (isFlippedSync(tstSync)) {
         do_flip = true;
      } else {
         ok = false;
      }
   }

   if (isSync(msgSize)) {
      std::cerr << "This is not supposed to be a sync word" << std::endl;
      ok = false;
   }

   if (do_flip) {
      msgSize = flip(msgSize);
   }
}

if (ok) {
   Size_msg_buf_to(msgSize); // enlarges object's buffer iff it's too small.
   // it would throw an exception if it failed.
}

if (ok && (*i_file)) {
   readLen = i_file->read(msg_buf, msgSize).gcount();
   ok = (readLen / msgSize > 0);
}

// END SNIP


OK, so that's what I'm doing.

// END BACKGROUND

Here's the mystery:

When I run the first utility to a file `cat file | util1 > msgFile` and then feed that file to the second utility `cat msgFile | util2` it works just fine. So, I know the writes are all happening in the proper order. But, if I pipe the data between the utilities directly `cat file | util1 | util2` it doesn't work! I appear to be reading the sync word over and over again instead of reading messages (my "This is not supposed to be a sync word" message spews out, apparently for every read). The first utility runs without complaints (the "COMPLAINT" messages don't come out). When I look in the wrong logfile, I don't see the "COMPLAINT" messages. When I look in the right logfile, I find that it thinks it's failing to send the sync word.

Now, my understanding of Linux pipes is that, first, they're supposed to deliver the data in order. Second, if the buffer is empty, read operations at the terminus block. Third, if the buffer is full, write operations at the source block. Fourth, they use a 64K buffer, which is way more than I need for my little messages, so it's not like they wouldn't fit. So, in short, I shouldn't be seeing buffer overflows, and I don't know what else would be different between writing to file and writing to a pipe.

So, it works if I use std::cout.write and std::cin.read with all this code placed directly in my utility. And it works with (istream*)&std::cin and (ostream*)&std::cout from my maps if I pipe the util1 output to a file and pipe the file to the input of util2, but it doesn't work with (istream*)&std::cin and (ostream*)&std::cout from my maps if I pipe the util1 output to the input of util2.

This is driving me nuts! Can anybody tell me what is going on?

Thanks!
Last edited on
Figured this out with the help of Barmar on stackoverflow.

It turns out you can't tellp() a pipe.

If I just check whether the stream is still good, it works fine.

I would still like to be able to know how many bytes went out with each write, but I suppose it's either all of them (good stream) or none of them (bad stream). I'm not 100% confident of that none of them answer, though.
Last edited on
Topic archived. No new replies allowed.