In your code, you have:
int result;
struct map*; /* mmapped array of structs */
You previously defined struct med
(and not struct map
), and you tried to define a variable of type struct map *
but gave it no name. The compiler is right to complain. You probably need something like:
int result;
struct med *map = 0; /* mmapped array of struct med */
Julia commented:
I now have the following errors:
invalid use of undefined type "struct med"
deferencing pointer to incomplete type
in map[i]=&s
and map[i]=&m
.
Well, I didn't look much further than the first error, but where's there's one, there are usually many.
For example:
map = (struct*)mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
needs the cast changed to (struct med *)
.
map[i]=&s;
is wrong. Semantically, map[i] = s;
would be correct (copy the data from s
into the mapped array) — but, on second thoughts, the assignment is simply wrong. Don't do it; delete that line.
Your FILESIZE
should probably be a multiple of sizeof(struct map)
rather than a multiple of sizeof(int)
.
The read()
on fd
is dubious at best — remove it; the whole point of mmap()
is to avoid direct file I/O.
The key values are integers; you don't use strcmp()
to compare them, and you simply use:
if (m.key == map[i].key)
The assignment map[i]=&m;
is wrong, again, and should be deleted, and the write()
is wrong. You have an array, map
, and you access it like you would any other array, and the system handles the I/O behind the scenes. Note that you should always check that read()
and write()
operations worked, but that becomes moot when you delete those operations anyway.
You will need to review how values are assigned to the map
array, and ensure you don't try to read from uninitialized values in the map
array.
There may still be other problems; I've not compiled, much less run, the code. But these comments should help you on your way.
Note that the error messages from the compiler should be helping too; you need to learn how to interpret them. Make sure you're compiling with enough warnings and that you're fixing all the warnings. If you use GCC, then gcc -Wall -Wextra -Werror -std=c11
is a fairly good start (I usually add a bunch of warnings about prototypes and function definitions too — some, but not all, of which are included in the above). Remember, the C compiler knows a lot more about C than you do at this stage of your career; listen to its warnings with care, because you should assume it is correct and you've made a mistake. That's what I do, and I've been coding in C for more than 30 years now.
Working code mm7.c
The created program is mm7
(memory map 7; there's no significance to 7 other than it is prime, small and makes the name unique).
A fair amount of the code is left as written in the question, rather than rewrite from scratch. Note that the code is careful to validate input — it detects and bails out on EOF, rather than blithely continuing and doing nothing useful, for example. It includes an output loop to print the stored data. It uses functions to manage parts of the input. It would be better if all the input were handled in functions.
#include
#include
#include
#include
#include
#include
#include
#define FILEPATH "/tmp/mmapped.bin"
#define NUMINTS (1000)
#define FILESIZE (NUMINTS * sizeof(struct med))
struct med
{
int key;
char name[25];
int quant_min;
int quant;
};
static void print_med(const char *tag, const struct med *med)
{
printf("%s: %4d: Q(%2d, min %2d): %s\n",
tag, med->key, med->quant, med->quant_min, med->name);
}
static int med_in_map(const struct med *map, int num_meds, int key)
{
for (int i = 0; i < num_meds; ++i)
{
if (key == map[i].key)
{
printf("Med %d already exists.\n", key);
return 1;
}
}
return 0;
}
static int get_new_key(const struct med *map, int num_meds, int *key)
{
while (printf("Key of med: ") > 0 && scanf("%d", key) == 1)
{
if (med_in_map(map, num_meds, *key) == 0)
return 0;
}
return EOF;
}
int main(void)
{
int fd;
int result;
struct med *map; /* mmapped array of structs */
/* Open a file for writing.
* - Creating the file if it doesn't exist.
* - Truncating it to 0 size if it already exists. (not really needed)
*
* Note: "O_WRONLY" mode is not sufficient when mmapping.
*/
fd = open(FILEPATH, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
if (fd == -1)
{
perror("Error opening file for writing");
exit(EXIT_FAILURE);
}
/* NB: ftruncate(fd, FILESIZE); is simpler */
/* Stretch the file size to the size of the (mmapped) array of structs */
result = lseek(fd, FILESIZE - 1, SEEK_SET);
if (result == -1)
{
close(fd);
perror("Error calling lseek() to 'stretch' the file");
exit(EXIT_FAILURE);
}
/* Something needs to be written at the end of the file to
* have the file actually have the new size.
* Just writing an empty string at the current file position will do.
*
* Note:
* - The current position in the file is at the end of the stretched
* file due to the call to lseek().
* - An empty string is actually a single '\0' character, so a zero-byte
* will be written at the last byte of the file.
*/
result = write(fd, "", 1);
if (result != 1)
{
close(fd);
perror("Error writing last byte of the file");
exit(EXIT_FAILURE);
}
/* Now the file is ready to be mmapped. */
map = (struct med *)mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED)
{
close(fd);
perror("Error mmapping the file");
exit(EXIT_FAILURE);
}
/* Input loop */
int num_meds;
for (num_meds = 0; num_meds < NUMINTS; num_meds++)
{
struct med m;
memset(&m, '\0', sizeof(m));
if (get_new_key(map, num_meds, &m.key) == EOF)
break;
printf("Name of med: ");
if (scanf("%s", m.name) != 1)
break;
printf("Quant. min. of med: ");
if (scanf("%d", &m.quant_min) != 1)
break;
printf("Quant. of med: ");
if (scanf("%d", &m.quant) != 1)
break;
map[num_meds] = m;
printf("Med %d saved.\n", m.key);
}
/* Output loop */
printf("\nRecorded meds:\n");
for (int i = 0; i < num_meds; i++)
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "M%.4d", i);
print_med(buffer, &map[i]);
}
/* Don't forget to free the mmapped memory */
if (munmap(map, FILESIZE) == -1)
{
perror("Error un-mmapping the file");
/* Decide here whether to close(fd) and exit() or not. Depends... */
}
/* Un-mmapping doesn't close the file, so we still need to do that. */
close(fd);
/* Remove file? */
/* unlink(FILEPATH); */
}
Example data input file mm7.data
This simulates terminal input with attempted repeats of the number.
1 Hydrocontin 3 5
1
2 Paxodontin 1 1
2
37 Ibuprofen 2 12
129 Butoxydione 12 29
4 Placebo 2 22
37
129
4
2
1
9231 Aspirin 99 99
Example run with the data file:
$ ./mm7 < mm7.data
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 1 saved.
Key of med: Med 1 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 2 saved.
Key of med: Med 2 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 37 saved.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 129 saved.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 4 saved.
Key of med: Med 37 already exists.
Key of med: Med 129 already exists.
Key of med: Med 4 already exists.
Key of med: Med 2 already exists.
Key of med: Med 1 already exists.
Key of med: Name of med: Quant. min. of med: Quant. of med: Med 9231 saved.
Key of med:
Recorded meds:
M0000: 1: Q( 5, min 3): Hydrocontin
M0001: 2: Q( 1, min 1): Paxodontin
M0002: 37: Q(12, min 2): Ibuprofen
M0003: 129: Q(29, min 12): Butoxydione
M0004: 4: Q(22, min 2): Placebo
M0005: 9231: Q(99, min 99): Aspirin
$
The prompts are all on one line for each actual medication because the input was read from a file, not from the keyboard.
Hex dump of data file /tmp/mmapped.bin
$ odx /tmp/mmapped.bin
0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00 ....Hydrocontin.
0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 00 00 00 00 00 00 00 00 dontin..........
0x0040: 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ................
0x0050: 25 00 00 00 49 62 75 70 72 6F 66 65 6E 00 00 00 %...Ibuprofen...
0x0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0070: 02 00 00 00 0C 00 00 00 81 00 00 00 42 75 74 6F ............Buto
0x0080: 78 79 64 69 6F 6E 65 00 00 00 00 00 00 00 00 00 xydione.........
0x0090: 00 00 00 00 00 00 00 00 0C 00 00 00 1D 00 00 00 ................
0x00A0: 04 00 00 00 50 6C 61 63 65 62 6F 00 00 00 00 00 ....Placebo.....
0x00B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00C0: 02 00 00 00 16 00 00 00 0F 24 00 00 41 73 70 69 .........$..Aspi
0x00D0: 72 69 6E 00 00 00 00 00 00 00 00 00 00 00 00 00 rin.............
0x00E0: 00 00 00 00 00 00 00 00 63 00 00 00 63 00 00 00 ........c...c...
0x00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
* (2484)
0x9C40:
$
The memset()
was added because the hex-dump showed garbage in the data structure — harmless garbage, but garbage nonetheless:
0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00 ....Hydrocontin.
0x0010: 2F 62 69 6E 3A 2F 75 73 72 2F 67 6E 75 2F 62 69 /bin:/usr/gnu/bi
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 2F 62 69 6E 3A 2F 75 73 dontin../bin:/us
0x0040: 72 2F 67 6E 75 2F 62 69 01 00 00 00 01 00 00 00 r/gnu/bi........
…
Initializing the structure with:
struct med m = { .key = 0, .name = "", .quant = 0, .quant_min = 0 };
didn't completely eliminate the garbage because of the padding bytes in the structure after the name (which surprised me!):
0x0000: 01 00 00 00 48 79 64 72 6F 63 6F 6E 74 69 6E 00 ....Hydrocontin.
0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 2F 62 69 ............./bi
0x0020: 03 00 00 00 05 00 00 00 02 00 00 00 50 61 78 6F ............Paxo
0x0030: 64 6F 6E 74 69 6E 00 00 00 00 00 00 00 00 00 00 dontin..........
0x0040: 00 00 00 00 00 2F 62 69 01 00 00 00 01 00 00 00 ...../bi........
…
Working with a non-empty file
The code no longer includes O_TRUNC
when creating the file, but that makes the hard-coded file name less appropriate (but I've not fixed that). The find_num_entries()
function simply does a linear search through the data to find where the list left off last time. More sophisticated code would do a binary search through the data to find where the empty entries start. There's also linear search in the lookup for keys. It would be better to keep the file in sorted order so that a binary search can be used for that, too. Indeed, it might be sensible to have a header record in a production version which had a magic number to identify the file type (if the file doesn't start with the correct magic number, it clearly isn't a file that this program wrote, so its contents shouldn't be interpreted), and to record other details such as number of entries and maybe minimum and maximum key values.
#include
#include
#include
#include
#include
#include
#include
#define FILEPATH "/tmp/mmapped.bin"
#define NUM_MEDS (1000)
#define FILESIZE (NUM_MEDS * sizeof(struct med))
struct med
{
int key;
char name[25];
int quant_min;
int quant;
};
static void print_med(const char *tag, const struct med *med)
{
printf("%s: %4d: Q(%2d, min %2d): %s\n",
tag, med->key, med->quant, med->quant_min, med->name);
}
static int med_in_map(const struct med *map, int num_meds, int key)
{
for (int i = 0; i < num_meds; ++i)
{
if (key == map[i].key)
{
printf("Med %d already exists.\n", key);
return 1;
}
}
return 0;
}
static int get_new_key(const struct med *map, int num_meds, int *key)
{
while (printf("Key of med: ") > 0 && scanf("%d", key) == 1)
{
if (med_in_map(map, num_meds, *key) == 0)
return 0;
}
return EOF;
}
static int find_num_entries(const struct med *map, int max_meds)
{
int i;
for (i = 0; i < max_meds; i++)
{
if (map[i].key == 0)
break;
}
return i;
}
int main(void)
{
int fd;
int result;
struct med *map;
fd = open(FILEPATH, O_RDWR | O_CREAT, (mode_t)0600);
if (fd == -1)
{
perror("Error opening file for writing");
exit(EXIT_FAILURE);
}
result = ftruncate(fd, FILESIZE);
if (result == -1)
{
close(fd);
perror("Error calling ftruncate() to 'set' the file size");
exit(EXIT_FAILURE);
}
map = (struct med *)mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED)
{
close(fd);
perror("Error mmapping the file");
exit(EXIT_FAILURE);
}
/* Input loop */
int num_meds;
for (num_meds = find_num_entries(map, NUM_MEDS); num_meds < NUM_MEDS; num_meds++)
{
struct med m;
memset(&m, '\0', sizeof(m));
if (get_new_key(map, num_meds, &m.key) == EOF)
break;
printf("Name of med: ");
if (scanf("%s", m.name) != 1)
break;
printf("Quant. min. of med: ");
if (scanf("%d", &m.quant_min) != 1)
break;
printf("Quant. of med: ");
if (scanf("%d", &m.quant) != 1)
break;
map[num_meds] = m;
printf("Med %d saved.\n", m.key);
}
/* Output loop */
printf("\nRecorded meds:\n");
for (int i = 0; i < num_meds; i++)
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "M%.4d", i);
print_med(buffer, &map[i]);
}
/* Don't forget to free the mmapped memory */
if (munmap(map, FILESIZE) == -1)
{
perror("Error un-mmapping the file");
/* Decide here whether to close(fd) and exit() or not. Depends... */
}
/* Un-mmapping doesn't close the file, so we still need to do that. */
close(fd);
return 0;
}
You can also make the error reporting simpler using functions analogous to err_syserr()
, which you can find in answers of mine such as Creating a file with given size.