Reputation: 1425
I am developing a client/server application in C, in which multiple clients send requests through a common FIFO.
The data being sent to the server is a struct containing multiple parameters including a pointer to an array of ints (pref_seat_list).
struct request {
pid_t pid;
int num_wanted_seats;
int *pref_seat_list; // <-- where the issue lies
int pref_seats_size;
};
The client sends the data through the FIFO but, on the server's end, the pointer points to a random/invalid address (understandable).
I could fix the issue by making pref_seat_list a fixed size array
int pref_seat_list[size]
, but it must behave as a dynamic size array that is initialized and allocated on the client side.
Is there a workaround for the pointer to somehow go through the FIFO without having to make its size fixed?
------------------------------------------------- FIX 1 -------------------------------------------------------
The struct now looks like this
struct request {
pid_t pid;
int num_wanted_seats;
int pref_seats_size;
int pref_seat_list[];
};
The initialization of the struct has been done: // Getting the size of pref_seat_list so we can initialize and allocate the array
// CLIENT.C
int pref_seats_size = count_seats_list(arglist[3]);
// Allocating space for request struct's pref_seat_list array (flexible array member)
struct request *req = malloc(sizeof(struct request) + sizeof(int[pref_seats_size]));
if (req == NULL)
{
fprintf(stderr, "Fatal: failed to allocate memory for the struct request");
exit(0);
}
Filling the struct's fields
// CLIENT.C
// Getting client's PID
req->pid = getpid();
// Getting client's number of wanted seats
char *end;
req->num_wanted_seats = strtol(arglist[2], &end, 10);
// Assigning size of the struct's pref_seat_list array
req->pref_seats_size = pref_seats_size;
// Assigning list of seats to struct's pref_seat_list dynamic array
int i = 0;
char *token = strtok(arglist[3], " ");
while (token != NULL)
{
req->pref_seat_list[i++] = strtol(token, &end, 10);
token = strtok(NULL, " ");
}
Expected output (SERVER.C)
1, 2, 3, 5, 8, 10, 12
Actual output (SERVER.C) - random values
3250, 0, 0, 123131, 1, 345691, 1
Upvotes: 0
Views: 2039
Reputation: 755094
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.
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);
}
$ 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).
Upvotes: 2
Reputation: 727127
There is no workaround for this: you would need to develop code that serializes and deserializes the struct
correctly. Both ends must agree on a uniform compiler-independent representation of data being exchanged.
You cannot simply ship a struct
to another process, because the receiving end may have different different memory alignment and sizing requirements.
The process of serializing the data has three steps:
struct
, and adding up its size requirements. In your case there is only one pointer, and the size is p->pref_seats_size * sizeof(*p->pref_seat_list)
char
s to fit your datastruct
members again, and store the data into the buffer.Once you get the buffer on the opposite side, allocate the struct
, walk through the data, and write the data back into the struct
.
Upvotes: 2