Outline solution
You'll need to handle the list of preferred seats separately from the main structure. Using a flexible array member (FAM) from C99 and beyond might be the simplest. The FAM must be the last member of the structure.
struct request
{
pid_t pid;
int num_wanted_seats;
int pref_seats_size;
int pref_seat_list[];
};
You allocate space for a request with num_prefs
preferred seats with:
struct request *rp = malloc(sizeof(*rp) + num_prefs * sizeof(rp->pref_seat_list[0]));
Note that sizeof(struct request)
(aka sizeof(*rp)
) does not include the size of the array (though it might include some padding that wouldn't be present if the FAM was not present, though that's not a problem here).
After checking that the allocation succeeded, you fill in the structure and array with the information required (copying num_prefs
into rp->pref_seats_size
). You can then write it all at once with:
fwrite(rp, sizeof(*rp) + rp->pref_seats_size * sizeof(rp->pref_seat_list[0])), 1, fp);
Now, how does the reading code handle this? It doesn't know how big a space to allocate until it has read the struct record
main information, so you have to take two bites at the cherry:
struct request req_hdr; // No space for the FAM
struct request *rp;
if (fread(&req_hdr, sizeof(req_hdr), 1, fp) != 1)
…EOF or other problems…
rp = malloc(sizeof(*rp) + req_hdr->pref_seats_size * sizeof(rp->pref_seat_list[0]));
// … error check allocation …
*rp = req_hdr;
if (fread(rp->pref_seat_list, sizeof(rp->pref_seat_list[0]) * rp->pref_seats_size, 1, fp) != 1)
…Protocol error…
The first read gets the fixed-size data, which also tells the receiving process how much variable-length data will follow. It allocates the correct space, and then reads the variable-length data into the allocated structure.
Note that this does assume that the process on the receiving end has the same size characteristics as the sending process. Since you're using a FIFO, your I/O is on a single machine, but there could, in theory, be problems with other types if the sending process is 64-bit and the receiving process is 32-bit, or vice versa — except that you're dealing with int
types (assuming pid_t
is int
or unsigned
in disguise) which will probably be all 32-bit regardless of 32-bit vs 64-bit issues. With other types or more complex structures, or if you were using a network connection rather than a local connection, you would have to work harder to get the data sent accurately in all circumstances.
POC Code
This code is available in my SOQ (Stack Overflow Questions) repository on GitHub as files send29.c
, recv29.c
, dumpreq.c
and request.h
in the src/so-5030-9324 sub-directory.
This code use my standard error reporting functions, which are also available in my SOQ repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory.
request.h
#ifndef REQUEST_H_INCLUDED
#define REQUEST_H_INCLUDED
#define FIFO_NAME "seat-request.fifo"
struct request
{
int pid;
int num_wanted_seats;
int pref_seats_size;
int pref_seat_list[];
};
extern void dump_request(const char *tag, const struct request *rp);
#endif /* REQUEST_H_INCLUDED */
send29.c
#include "request.h"
#include "stderr.h" /* See https://github.com/jleffler/soq/tree/master/src/libsoq */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> /* mkfifo() */
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc > 0) // Use argc - avoid unused argument warning
err_setarg0(argv[0]);
/* Maybe the other program already created it? */
if (mkfifo(FIFO_NAME, 0666) != 0 && errno != EEXIST)
err_syserr("failed to create FIFO %s: ", FIFO_NAME);
FILE *fp = fopen(FIFO_NAME, "w");
if (fp == NULL)
err_syserr("failed to open FIFO %s for writing: ", FIFO_NAME);
printf("Send: PID %d at work with FIFO %s open for writing\n", (int)getpid(), FIFO_NAME);
struct request *rp = 0;
int num_prefs = 10;
size_t req_size = sizeof(*rp) + num_prefs * sizeof(rp->pref_seat_list[0]);
rp = malloc(req_size);
if (rp == 0)
err_syserr("failed to allocate %zu bytes memory: ", req_size);
rp->pid = getpid();
rp->num_wanted_seats = 3;
rp->pref_seats_size = num_prefs;
for (int i = 0; i < num_prefs; i++)
rp->pref_seat_list[i] = 123 + i;
dump_request("Sender", rp);
if (fwrite(rp, req_size, 1, fp) != 1)
err_syserr("failed to write request (%zu bytes) to FIFO %s: ", req_size, FIFO_NAME);
free(rp);
fclose(fp);
unlink(FIFO_NAME);
printf("Send: PID %d finished writing %zu bytes to FIFO %s\n", (int)getpid(), req_size, FIFO_NAME);
return 0;
}
recv29.c
#include "request.h"
#include "stderr.h" /* See https://github.com/jleffler/soq/tree/master/src/libsoq */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> /* mkfifo() */
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc > 0) // Use argc - avoid unused argument warning
err_setarg0(argv[0]);
/* Maybe the other program already created it? */
if (mkfifo(FIFO_NAME, 0666) != 0 && errno != EEXIST)
err_syserr("failed to create FIFO %s: ", FIFO_NAME);
int fd = open(FIFO_NAME, O_RDONLY);
if (fd < 0)
err_syserr("failed to open FIFO %s for reading: ", FIFO_NAME);
printf("Recv: PID %d at work with FIFO %s open for reading\n", (int)getpid(), FIFO_NAME);
struct request req;
struct request *rp = 0;
if (read(fd, &req, sizeof(req)) != sizeof(req))
{
/* Marginally dubious error reporting; if the return value is
** positive but small, errno has no useful information in it.
*/
err_syserr("failed to read %zu bytes for head from FIFO %s: ", sizeof(req), FIFO_NAME);
}
size_t req_size = sizeof(*rp) + req.pref_seats_size * sizeof(rp->pref_seat_list[0]);
rp = malloc(req_size);
if (rp == 0)
err_syserr("failed to allocate %zu bytes memory: ", req_size);
*rp = req;
int nbytes = rp->pref_seats_size * sizeof(rp->pref_seat_list[0]);
//if (read(fd, &rp->pref_seat_list[0], nbytes) != nbytes)
if (read(fd, rp->pref_seat_list, nbytes) != nbytes)
err_syserr("failed to read %d bytes for body from FIFO %s: ", nbytes, FIFO_NAME);
dump_request("Receiver", rp);
free(rp);
close(fd);
unlink(FIFO_NAME);
printf("Recv: PID %d finished reading request from FIFO %s\n", (int)getpid(), FIFO_NAME);
return 0;
}
dumpreq.c
#include "request.h"
#include <stdio.h>
void dump_request(const char *tag, const struct request *rp)
{
printf("%s:\n", tag);
printf("- PID requesting seats: %d\n", rp->pid);
printf("- Number of seats wanted: %d\n", rp->num_wanted_seats);
printf("- Number of seats in preferred list: %d\n", rp->pref_seats_size);
for (int i = 0; i < rp->pref_seats_size; i++)
printf(" %d is seat %d\n", i, rp->pref_seat_list[i]);
fflush(stdout);
}
Sample run
$ send29 & recv29
[1] 55896
Send: PID 55896 at work with FIFO seat-request.fifo open for writing
Sender:
- PID requesting seats: 55896
- Number of seats wanted: 3
- Number of seats in preferred list: 10
0 is seat 123
1 is seat 124
2 is seat 125
3 is seat 126
4 is seat 127
5 is seat 128
6 is seat 129
7 is seat 130
8 is seat 131
Recv: PID 55897 at work with FIFO seat-request.fifo open for reading
9 is seat 132
Receiver:
- PID requesting seats: 55896
- Number of seats wanted: 3
- Number of seats in preferred list: 10
0 is seat 123
1 is seat 124
2 is seat 125
3 is seat 126
4 is seat 127
5 is seat 128
6 is seat 129
7 is seat 130
8 is seat 131
9 is seat 132
Send: PID 55896 finished writing 52 bytes to FIFO seat-request.fifo
Recv: PID 55897 finished reading request from FIFO seat-request.fifo
[1]+ Done send29
$
You can run the programs in either order (so recv29 & send29
also works).