tags:

views:

105

answers:

3

I've been tasked with updating a function which currently reads in a configuration file from disk and populates a structure:

static int LoadFromFile(FILE *Stream, ConfigStructure *cs)
{
  int tempInt;

   ...

  if ( fscanf( Stream, "Version: %d\n",&tempInt) != 1 )
  {
    printf("Unable to read version number\n");
    return 0;
  }
  cs->Version = tempInt;
   ...

}

to one which allows us to bypass writing the configuration to disk and instead pass it directly in memory, roughly equivalent to this:

static int LoadFromString(char *Stream, ConfigStructure *cs)

A few things to note:

  • The current LoadFromFile function is incredibly dense and complex, reading dozens of versions of the config file in a backward compatible manner, which makes duplication of the overall logic quite a pain.
  • The functions that generate the config file and those that read it originate in totally different parts of the old system and therefore don't share any data structures so I can't pass those directly. I could potentially write a wrapper, but again, it would need to handle any structure passed in in a backwards compatible manner.
  • I'm tempted to just pass the file as is in as a string (as in the prototype above) and convert all the fscanf's to sscanf's but then I have to handle incrementing the pointer along (and potentially dealing with buffer overrun errors) manually.
  • This has to remain in C, so no C++ functionality like streams can help here

Am I missing a better option? Is there some way to create a FILE * that actually just points to a location in memory instead of on disk? Any pointers, suggestions or other help is greatly appreciated.

+1  A: 
  • Use mkstemp() to create a temporary file. It takes char * as argument and uses it as a template for the file's name. But, it will return a file descriptor.

  • Use tmpfile(). It returns FILE *, but has some security issues and also, you have to copy the string yourself.

  • Use mmap() ( Beej's Guide for help)

N 1.1
`mkstemp` uses its argument as a template for the temporary file's name, not its contents--e.g., `/tmp/fileXXXXXX.tmp` turns into `/tmp/fileHa7110.tmp`.
John Kugelman
This would be simplest. Create the temp file, write the string to it and use the original LoadFromFile(). There's very little new code and what new code there is, is simple. In other words, low probability of introducing new bugs and easy to maintain.
JayM
How does this allow the OP to bypass writing the configuration to disk and instead pass it directly in memory?
jschmier
@Kugelman: updated.
N 1.1
@Dusty: i guess than you should check out `mmap()`. answer updated.
N 1.1
+3  A: 

If you can't pass structures and must pass the data as a string, then you should be able to tweak your function to read from a string instead of a file. If the function is as complicated as you describe, then converting fscanf->sscanf would possibly be the most straightforward way to go.

Here's an idea using your function prototype above. Read in the entire data string (without processing any of it) and store it in a local buffer. That way the code can have random access to the data as it can with a file and making buffer overruns easier to predict and avoid. Start by mallocing a reasonably-sized buffer, copy data into it, and realloc yourself more space as needed. Once you have a local copy of the entire data buffer, scan through it and extract whatever data you need.

Note that this might get tricky if '\0' characters are valid input. In that case, you would have to add additional logic to test if this was the end of the input string or just a zero byte (difficulty depending on the particular format of your data buffer).

bta
fscanf moves the file pointer as it reads allowing for repeated and sequential fscanf calls, sscanf does not. How does one replicate the fscanf functionality with sscanf for stepping through the string?
Mark E
The `%n` format specifier will make `scanf` and friends write the number of characters read (by this particular call to `scanf`) into the associated argument. Use a pointer to keep track of the current read location in the input buffer (emulating a file pointer) and use `%n` at the end of every `sscanf` format string so you will know how far to advance the pointer for the next `sscanf` call.
bta
@bta awesome, I've never seen `%n` used (+1)
Mark E
+1  A: 

Since you are trying to keep the file data in memory, you should be able to use shared memory. POSIX shared memory is actually a variation of mapped memory. The shared memory object can be mapped into the process address space using mmap() if necessary. Shared memory is usually used as an IPC mechanism, but you should be able to use it for your situation.

The following example code uses POSIX shared memory (shm_open() & shm_unlink()) in conjunction with a FILE * to write text to the shared memory object and then read it back.

#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>

#define MAX_LEN 1024

int main(int argc, char ** argv)
{
    int    fd;
    FILE * fp;
    char * buf[MAX_LEN];

    fd = shm_open("/test", O_CREAT | O_RDWR, 0600);

    ftruncate(fd, MAX_LEN);

    fp = fdopen(fd, "r+");

    fprintf(fp, "Hello_World!\n");

    rewind(fp);

    fscanf(fp, "%s", buf);

    fprintf(stdout, "%s\n", buf);

    fclose(fp);

    shm_unlink("/test");

    return 0;
}

Note: I had to pass -lrt to the linker when compiling this example with gcc on Linux.

jschmier
That'll do it, thanks! Is there any significance to the name given, or is it just a handle? I assume since this is for IPC that any process using the same one gets the same object? So to avoid collisions I'll need to make the handle name unique?
Dusty
That is correct. The *name* is a string naming a shared memory object. For portable use, the *name* should have an initial slash (/) and contain no embedded slashes. Consult the documentation for more details.
jschmier