GRoutar
GRoutar

Reputation: 1425

Pass struct pointer element to FIFO - C

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

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 755094

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).

Upvotes: 2

Sergey Kalinichenko
Sergey Kalinichenko

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:

  • Figure out how much memory you are going to need by walking through each pointer in the 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)
  • Allocate the buffer of chars to fit your data
  • Walk through the struct 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

Related Questions