Forking with Pipes - c

I have tried to do fork() and piping in main and it works perfectly fine but when I try to implement it in a function for some reason I don't get any output, this is my code:
void cmd(int **pipefd,int count,int type, int last);
int main(int argc, char *argv[]) {
int pipefd[3][2];
int i, total_cmds = 3,count = 0;
int in = 1;
for(i = 0; i < total_cmds;i++){
pipe(pipefd[count++]);
cmd(pipefd,count,i,0);
}
/*Last Command*/
cmd(pipefd,count,i,1);
exit(EXIT_SUCCESS);
}
void cmd(int **pipefd,int count,int type, int last){
int child_pid,i,i2;
if ((child_pid = fork()) == 0) {
if(count == 1){
dup2(pipefd[count-1][1],1); /*first command*/
}
else if(last!=1){
dup2(pipefd[count - 2][0],0); /*middle commands*/
dup2(pipefd[count - 1][1],1);
}
else if(last == 1){
dup2(pipefd[count - 1][0],0); /*last command*/
}
for(i = 0; i < count;i++){/*close pipes*/
for(i2 = 0; i2 < 2;i2++){
close(pipefd[i][i2]);
}}
if(type == 0){
execlp("ls","ls","-al",NULL);
}
else if(type == 1){
execlp("grep","grep",".bak",NULL);
}
else if(type==2){
execl("/usr/bin/wc","wc",NULL);
}
else if(type ==3){
execl("/usr/bin/wc","wc","-l",NULL);
}
perror("exec");
exit(EXIT_FAILURE);
}
else if (child_pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
}
I checked the file descriptors and it is opening the right ones, not sure what the problem
could be..
Edit: I Fixed the problem but I'm having child processes, which way would be the best to wait for the child , while(wait(NULL)!=-1); but that hangs

The problem is that pipefd is not an int**, it's an int[3][2], so when you pass it to cmd, you get garbage. Your compiler should be giving you a warning on each call to cmd(), such as something like this:
warning: passing argument 1 of 'cmd' from incompatible pointer type
If not, turn up your warning level.
It's true that arrays decay into pointers to their first elements, so you can pass a 1-D array to a function expecting a pointer, but that's only true for the first dimension of arrays. In particular, a 2D array does not decay into a pointer to a pointer. It decays at the first level only, so pipefd can decay into the type int (*)[2], which is read as "pointer to array 2 of int"
So, the correct way to write cmd is this:
void cmd(int (*pipefd)[2],int count,int type, int last)

Related

How to properly write an array of structs to a pipe in C

I have a hard time figuring out how to pass an array of structs with strings in them through a pipe to a child process.
I created two demos to show my problem.
demo_int.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t child;
int pfd[2];
if (pipe(pfd) == -1)
{
exit(1);
}
child = fork();
if (child < 0)
{
exit(1);
}
if (child == 0)
{
close(pfd[1]);
int *arr = malloc(10 * sizeof(int));
if (arr == NULL)
{
exit(1);
}
read(pfd[0], arr, 10 * sizeof(int));
close(pfd[0]);
printf("child process read:\n");
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
free(arr);
exit(0);
}
else
{
int *arr = malloc(10 * sizeof(int));
if (arr == NULL)
{
exit(1);
}
for (int i = 0; i < 10; ++i)
{
arr[i] = i;
}
printf("array to be written:\n");
for (int i = 0; i < 10; ++i)
{
printf("%d\n", arr[i]);
}
close(pfd[0]);
write(pfd[1], arr, 10 * sizeof(int));
close(pfd[1]);
free(arr);
printf("parent process done\n");
wait(NULL);
}
}
I created this, so I can be sure that the problem is not with the "dynamic array" part, but with the "structs" part, and maybe more specifically the "string in a struct" part.
This produces the expected result:
array to be written:
0
1
2
3
4
5
6
7
8
9
parent process done
child process read:
0
1
2
3
4
5
6
7
8
9
With valgrind reporting no errors or leaks.
However when I try the same with the problematic structs:
demo_person.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
typedef struct Person
{
char *name;
int age;
} Person;
int main()
{
pid_t child;
int pfd[2];
if (pipe(pfd) == -1)
{
exit(1);
}
child = fork();
if (child < 0)
{
exit(1);
}
if (child == 0)
{
close(pfd[1]);
Person *arr = malloc(10 * sizeof(Person));
if (arr == NULL)
{
exit(1);
}
read(pfd[0], arr, 10 * sizeof(Person));
close(pfd[0]);
printf("child process read:\n");
for (int i = 0; i < 10; ++i)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
for (int i = 0; i < 10; ++i)
{
free(arr[i].name);
}
free(arr);
exit(0);
}
else
{
Person *arr = malloc(10 * sizeof(Person));
if (arr == NULL)
{
exit(1);
}
for (int i = 0; i < 10; ++i)
{
char *name = malloc(8 * sizeof(char));
if (name == NULL)
{
exit(1);
}
sprintf(name, "%s%d", "Person", i);
arr[i].name = malloc(8 * sizeof(char));
if (arr[i].name == NULL)
{
exit(1);
}
strcpy(arr[i].name, name);
arr[i].age = i;
free(name);
}
printf("array to be written:\n");
for (int i = 0; i < 10; ++i)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
close(pfd[0]);
write(pfd[1], arr, 10 * sizeof(Person));
close(pfd[1]);
for (int i = 0; i < 10; ++i)
{
free(arr[i].name);
}
free(arr);
printf("parent process done\n");
wait(NULL);
}
}
The output is:
array to be written:
Person0 0
Person1 1
Person2 2
Person3 3
Person4 4
Person5 5
Person6 6
Person7 7
Person8 8
Person9 9
parent process done
child process read:
0
1
2
3
4
5
6
7
8
9
free(): invalid pointer
With valgrind reporting loads of errors (as expected after this output).
I found similiar looking questions, but none of the answers seemed to help.
EDIT:
Thanks to the answer I now understand that the problem is with the dynamically allocated string and only the mallocing process can access it, but the real program in which I encountered this problem has been populated (kind of) like this, as in it already uses these dinamically allocated strings.
Is there a way to pass the strings like this, or do I have to solve it somehow with new char[N] arrays?
The memory you allocate with malloc and the pointer it returns are only valid in the process you do the call to malloc.
When you write the structure through the pipe you only write the (current process unique) pointer, not the memory it points to.
The quick and simple solution is to use an actual array instead:
typedef struct Person
{
char name[10];
int age;
} Person;
What you've stumbled upon is commonly solved using what's known as "serialization," which allows you to reliably send and receive data over a wire (pipe, network socket, file, etc). A popular serialization format is JSON, for its wide support and easy readability, but there's nothing stopping you from creating your own serialization format, and just using that!
A common way to pack binary data reliably is to use a header-payload format, where the header contains information about what kind of data is in the payload, and also how long the payload is. From there, it's as simple as reading in a fixed size header, parsing it, then reading the payload on the receiving end.
Something like this may work for you:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct simple_header {
char kind; /* I arbitrarily chose a char, which could use something like 's' for string, 'i' for int, etc. Commonly you will see an enum used here */
int length; /* You could use a negative length to indicate errors of some kind, or just use a simple size_t */
};
struct simple_payload {
unsigned char *data;
};
int serialize_string(int fd, const char *payload) {
// Automatically find the size, for convenience
size_t length = strlen(payload);
// Set aside a header and populate it
struct simple_header header;
header.kind = 's';
header.length = (int) length; // This could be checked better, but also just a simple example
// Send the header over the wire, doing minimal error checking
int ret = write(fd, &header, sizeof(header));
if(ret < 0) return ret;
// Send the payload
ret = write(fd, payload, length);
return ret;
}
int deserialize(int fd, struct simple_payload *destination) {
struct simple_header received_header;
int ret = read(fd, &received_header, sizeof(received_header));
if(ret < 0) return ret;
// This solution totally ignores endianness, which you will need to consider if sending and receiving on different computers
// Always work with zeroed buffers when you can, leave room for NULL bytes
destination->data = calloc(received_header.length + 1, 1);
ret = read(fd, destination->data, received_header.length);
if(ret < 0) {
free(destination->data);
return ret;
}
switch(received_header.kind) {
case 's':
/* do something special for strings */
;
default:
return -1; /* unsupported format */
}
return ret;
}
If this is anything more than a pet project, though, I'd recommend looking into serialization formats and their libraries (header-only will be easiest to integrate). With serialization, the devil really is in the details, the unhandled errors and endianness considerations can lead to data corruption, so if you value the data you're sending, please use a library! My included example does not cover:
when the header lies about payload length
payloads that exceed the length of whats in the header
Failed reads/writes, leading you to think you're reading a header when actually you're reading a payload
Error detection/correction (CRC, Reed-Solomon etc)
Struct alignment issues (packed vs unpacked)

My char vector is saving the same value in every position

Im trying to make a shell in linux. I need to save every command the user typed and when the user wants to see the history Ill show him the last 10 commands. Im using a char* historial[10]; but when the user type the second command it is saving in historial[0] and historial[1] the same thing. So if I type 10 different commands, it will save the last command 10 times. I dont know what Im doing wrong.
PS: my condition in the do while is not working neither.
int ejecutarCom(char* comando)
{ //executes the command
pid_t pid;
pid = fork();
int aux = 0;
if (pid < 0)
{
perror("Error");
return -1;
}
else if (pid == 0)
{
aux = execlp(comando, comando, NULL);
}
else
{
wait(NULL);
}
return aux;
}
char* historial[10]; //history vector
int cantidad = 0, tamano = 10; //quantity & size
void guardarEnHistorial(char* c)
{ //saves every command
if (cantidad < tamano)
{
historial[cantidad] = c;
}
if (cantidad == tamano)
{
cantidad = 0;
historial[cantidad] = c;
}
cantidad++;
}
void verHistorial()
{ //shows history
for (int i = 9; i >= 0; i--)
{
if (historial[0] == NULL)
break;
else if (historial[i] == NULL)
{
printf(" ");
}
else
printf("%i: %s\n", i, historial[i]);
}
}
int main()
{
char* comando = (char*) malloc(1024);
int x = 1;
do
{
printf("%s ", prompt);
fflush(stdout);
fgets(comando, sizeof(comando), stdin);
comando[strcspn(comando, "\n")] = '\0';
printf("%s\n", comando);
if (strcmp(comando, "hist") == 0)
{
verHistorial();
}
else
{
x = ejecutarCom(comando);
guardarEnHistorial(comando);
printf("%i\n", x);
}
} while (x != -1);
Modify your main function like below. Every location of your history array is pointing to same memory location hence you are getting the same information in every location. You have to allocate memory for each command like below:-
int main(){
char* comando=(char*) malloc(1024);
int x=1;
do{
printf("%s ",prompt);
fflush (stdout);
fgets(comando,sizeof(comando),stdin);
comando[strcspn(comando, "\n")] = '\0';
printf("%s\n",comando);
if(strcmp(comando,"hist")==0){
verHistorial();}
else{
x=ejecutarCom(comando);
guardarEnHistorial(comando);
comando=(char*) malloc(1024);// You need to allocate memory to store
//each history records. Also consider whether you really need 1024 bytes of memory make it small
printf("%i\n",x);
}}while(x!=-1);
You are storing a pointer to the command buffer, not the command itself. As the address of the command buffer doesn't change, but its contents does, every history entry will point to the same command buffer and thus will show the same command:
char* historial[10]; // history vector has only pointers
//...
historial[cantidad] = c; // stores a pointer
The following allocates and frees memory as appropriate:
void guardarEnHistorial(char* c)
{ //saves every command
if (cantidad == tamano) cantidad = 0;
if (historial[cantidad]) free(historial[cantidad]);
historial[cantidad]= malloc(strlen(c)+1);
strcpy(historial[cantidad], c);
cantidad++;
}
I dont know what Im doing wrong.
You are observing this behavior because the char pointers of historial array are pointing to same memory location comando. Check this statement of your program:
guardarEnHistorial(comando);
and in the guardarEnHistorial () function, you are doing:
historial[cantidad] = c;
Any changes you make to comando will reflect to all the members of historial array in which you have saved the command.
You can use strdup() to resolve this issue. Replace this:
guardarEnHistorial(comando);
with this:
guardarEnHistorial(strdup(comando));
^^^^
The strdup() function returns a pointer to a new string which is a duplicate of the string passed to it. strdup() allocates a memory for the new string using malloc and returns its pointer. Make sure to free it using free() once you are done with it. Like when inserting into historial array, you are rotating the insertion if the condition cantidad == tamano meets. Here, you need to free the element first and then insert the new element into the array otherwise there is a memory leaks in your program.

Unexpected Segmentation Fault in pthread Program

So a foreword, I am new to C, so please forgive any horrifying mistakes my program has.
I am trying to write a program that will take a list of numbers passed in as arguments to the program or contained in a file whose path is passed in.
It stores the numbers into an array, and stores how many numbers there are into the first element of the array. It will only store up to 100 numbers.
It then creates a pthread and passes the pointer to the array to the thread.
The thread is then suppose to sum up the numbers and return the sum back to the main function.
I am experiencing the following issues:
1. It doesn't always happen, but sometimes I get a segmentation fault right before the line of code that says:
printf("Begining to create the thread");
2. My attempts to return the sum isn't working, and after hours of research online, I can't figure out why.
3. When I compile the program, I get the following errors:
gcc -g -o assn3 assn3.c -pthread
assn3.c: In function ‘AddUpNumbers’:
assn3.c:34:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
return (void*)total;
^
assn3.c: In function ‘main’:
assn3.c:96:3: warning: passing argument 1 of ‘pthread_join’ makes integer from pointer without a cast [enabled by default]
pthread_join(&functionThread, &total);
^
In file included from assn3.c:12:0:
/usr/include/pthread.h:261:12: note: expected ‘pthread_t’ but argument is of type ‘pthread_t *’
extern int pthread_join (pthread_t __th, void **__thread_return);
^
assn3.c:97:3: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘void *’ [-Wformat=]
printf("The total returned by the thread is %d", total);
^
Here's my code:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NO_ARGS 1
#define ONLY_SINGLE_ARG 2
#define PATH_TO_READ_FROM 1
#define MAX_NUMBERS 101
#define MAX_CHAR_INPUT 255
#define COUNT_LOCATION 0
void* AddUpNumbers(void* arrayPointer) {
printf("Created the pthread!");
int* numbersArray = (int*)arrayPointer;
int count = numbersArray[COUNT_LOCATION];
int total = 0;
int i = 1;
while (i < count) {
total = total + numbersArray[i];
}
printf("The total to be returned is %d", total);
return (void*)total;
}
int main(int argc, char* argv[]) {
FILE * numbersFile = NULL;
int count = 0;
int numberArray[MAX_NUMBERS];
//Initialize the Array
int i = 0;
while (i < MAX_NUMBERS) {
numberArray[i] = 0;
i = i + 1;
}
if (argc == NO_ARGS) {
printf("Usage: # or file path, #, #, ..., #\n");
} else if (argc == ONLY_SINGLE_ARG) {
numbersFile = fopen(argv[PATH_TO_READ_FROM], "r");
if (numbersFile != NULL) {
char buff[MAX_CHAR_INPUT];
i = 1;
count = 0;
while (i < MAX_NUMBERS) {
if (fscanf(numbersFile, "%s", buff) != EOF) {
numberArray[i] = atoi(buff);
printf("%d\n", numberArray[i]);
i = i + 1;
count = count + 1;
} else {
break;
}
}
numberArray[COUNT_LOCATION] = count;
printf("Count Total: %d\n", numberArray[COUNT_LOCATION]);
} else {
printf("Error: Could not open file!\n");
return -1;
}
} else if (argc < MAX_NUMBERS + 1) {
i = 1;
count = 0;
while (i < argc) {
numberArray[i] = atoi(argv[i]);
printf("%d\n", numberArray[i]);
i = i + 1;
count = count + 1;
}
printf("See if error happens after this");
numberArray[COUNT_LOCATION] = count;
printf("Count Total: %d\n", numberArray[COUNT_LOCATION]);
} else {
printf("Too many numbers! This program can only add up to: %d numbers.\n", MAX_NUMBERS);
return -1;
}
printf("Begining to create the thread");
pthread_t functionThread;
int creationSuccess = 0;
void* total;
creationSuccess = pthread_create(&functionThread, NULL, AddUpNumbers, (void*)numberArray);
if (creationSuccess == 0) {
pthread_join(&functionThread, total);
printf("The total returned by the thread is %d", *((int)total));
} else {
printf("Something went wrong.\n");
}
if (numbersFile != NULL) {
fclose(numbersFile);
}
return 0;
}
My Makefile looks like this:
assn3: assn3.c
gcc -g -o assn3 assn3.c -pthread
You should be very wary of compiler warnings. Either clean them up or understand very well why they are ok. Pay special attention to warnings about data type mismatches.
In this case this warning probably explains the main problem:
In file included from assn3.c:12:0:
/usr/include/pthread.h:261:12: note: expected ‘pthread_t’ but argument is of type ‘pthread_t *’
extern int pthread_join (pthread_t __th, void **__thread_return);
^
You are (creating and) passing a pointer to your pthread_t object as the first argument of pthread_join(), but unlike pthread_create(), pthread_join() expects you to pass the pthread_t itself, not a pointer to it. All manner of havoc (technically, "undefined behavior") will ensue.
UPDATE: Additionally, the second argument you are passing to pthread_join() is an uninitialized pointer to void. If pthread_create() tries to write anything where it points then who knows what happens (undefined behavior again). You are expected to pass a valid pointer to the location where the result is to be written. In this case, that would be &total.
The syntax of pthread_create is:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, *(*start_routine) (void *), void *arg);
pthread_t - is id of thread. So create variable pthread_t id, or array of values if you have a lot of threads, like pthread_t id[THREAD_NUM];
Then your func will looks like:
pthread_create(&id[i], NULL, &functionThread, (void*)numberArray);
With pthread_join(&functionThread, total);
The same thing.
int pthread_join(pthread_t thread, void **value_ptr);
So you join must look like:
pthread_join(&id[i], total);

Segmentation Fault when accessing string array modified by a function in C

In my main, I define a pointer to an array of filename strings (that are to be determined):
char **commands = NULL;
I then call a function which populates the array based on argc & argv using getopt(). Before I return from the function, I print the list of files in the array and it works fine.
Back in my main, I attempt to print the same list (as confirmation) but I get a segmentation error which I can't figure out how to fix. I've included my code below.
Can someone please help me understand why commands in my main is not able to access the file list.
Cheers,
Nap
Here is the function that builds the list of file names:
int getInput (int argc, char **argv, const char *optionList, int *number, int *showEnds, char **commands, int *numberCommands) {
char c;
opterr = 0; // Turn off automatic error messages.
while ( (c = getopt(argc, argv, optionList)) != -1) {
switch (c) {
case 'n':
*number = TRUE;
break;
case 'E':
*showEnds = TRUE;
break;
case '?':
printf("Invalid switch used \"%c\"\n", optopt);
return EXIT_FAILURE;
break;
default:
printf("Input error occurred");
return EXIT_FAILURE;
}
}
printf("optind = %d,%d\n",optind, argc);
commands = malloc(sizeof(char*) * (argc-1));
if (optind < argc) {
*numberCommands = (argc - optind);
int ndx = 1;
while (optind < argc) {
int arraySize = (ndx+1)*sizeof(char*);
commands = realloc (commands,arraySize);
if (commands == NULL) {
fprintf(stderr,"Realloc unsuccessful");
exit(EXIT_FAILURE);
}
int stringSize = strlen(argv[optind])+1;
commands[ndx] = malloc(stringSize);
if(commands[ndx]==NULL){
fprintf(stderr,"Malloc unsuccessful");
exit(EXIT_FAILURE);
}
strcpy(commands[ndx],argv[optind]);
ndx++;
optind++;
}
printf ("Done With Input\n");
for (int i=1; i<=*numberCommands; i++) {
printf ("%s\n",commands[i]);
}
}
return EXIT_SUCCESS;
}
Here is my main:
int main(int argc, char **argv) {
FILE *myInput;
FILE *myOutput;
int result;
const char availOptions[] = "nE";
// Option flags
int number = FALSE; // (Option n) number all output lines
int showEnds = FALSE; // (Option E) Show Ends
char **commands = NULL;
int numberCommands = 0;
// Set default to stdin/stdout.
myInput = stdin;
myOutput = stdout;
if (argc > 1) {
result = getInput(argc,argv, availOptions, &number, &showEnds, commands, &numberCommands);
if (result != EXIT_SUCCESS) {
exit(EXIT_FAILURE);
}
}
printf ("Number of files = %d\n", numberCommands);
for (int i=1; i<=numberCommands; i++) {
printf ("%s\n",commands[i]);
}
echoInput(myInput, myOutput);
}
You're passing in a copy of the double-pointer
char **commands;
into the function. When you malloc() memory for it, it's not updating the pointer in your main function, only your local copy of the double-pointer. Hence, you're attempting to deference a NULL pointer in main. You could instead pass in a pointer to the double-pointer (i.e. the function would take a triple-pointer), your code would work then.
You would need to dereference the triple-pointer inside the function like so:
*commands = malloc(sizeof(char*) * (argc-1));
Alternatively, make a new double-pointer inside your function, do your work (leaving the code in your function roughly as-is) and before you return, assign the memory back into the pointer passed in from main:
*commands = local_commands;
In this function:
int getInput (/* ... */, char **commands, int *numberCommands) {
/* ... */
commands = malloc(sizeof(char*) * (argc-1));
}
you accept a pointer-to-pointer-to-char parameter by value. Looking at the call site, we can see that value will always be (char**)NULL. The parameter is a local variable inside your function. Even though you change it, and even though the caller happens to have a variable with the same name, you never change the caller's copy.
If you want to change the caller's copy of commands, you need to pass the address of that variable to your function:
int getInput (/* ... */, char ***commands, int *numberCommands) {
/* ... */
*commands = malloc(sizeof(char*) * (argc-1));
}
Now the call site looks like this:
char **commands = NULL;
int numberCommands = 0;
/* ... */
if (argc > 1) {
result = getInput(/*...*/, &commands, &numberCommands);
if (result != EXIT_SUCCESS) {
exit(EXIT_FAILURE);
}
}
note that commands and numberCommands are the two out-parameters - that is, they're the parameters where the function should change your local variable - so you're using & to pass pointers to both of them.

C program pointers crashing system calls

// Struct for Country Data
typedef struct
{
char name[50]; // Country name
char code[3]; // Country code
int population; // Country Population
double lifeExp; // Country Life expectancy
} CountryData;
// Struct for Dir File
typedef struct
{
char code[3];
int offSet;
} DirData;
// Function Declarations
void fillCountryStructs(CountryData ** dataPtr, int nLines, int fd);
void fillDirectoryStructs(CountryData **dataPtr, DirData **director, int nLines,int fd2);
void sortStructs(DirData **director, int nLines);
int verifyString(char *s1, char *s2);
// Main Function
// - This function starts the program, get the number of lines as a
// parameter, fills the structs and writes the data to the Country
// File and the Directory file.
int main(int argc, char *argv[]) // Always remember to pass an argument while executing
{
// Some variables
int nLines; // The number of lines
char *pEnd; // For String functions
FILE *Fin,*Fout; // File pointers
int fd;
int fd2;
nLines = strtod(argv[1], &pEnd);
CountryData **countryDataPtr; // Array of structs
CountryData **tempStruct;
DirData **director;
// Allocate memory for the struct pointers
countryDataPtr = calloc(nLines, sizeof(CountryData*));
director = calloc(nLines, sizeof(DirData*));
// File Stream for "AllCountries.dat"
if((fd = open("AllCountries.dat", O_RDWR)) ==-1)
err_sys("File not found...\n");
// File Stream for "RandomStruct.bin"
if ((fd2 = open("RandomStruct.bin", O_RDWR)) == -1)
err_sys("Failed to open binary\n");
// Filling the Country stucts
fillCountryStructs(countryDataPtr, nLines, fd);
close (fd);
//fclose(Fin); // Closing the file "AllCountries.dat"
// Writing Binary File
write(fd2, (countryDataPtr[0]->name[0]), sizeof(CountryData));
close (fd2);
//fclose(Fout);
printf("RandomStruct.bin written Sucessfully\n");
// Filling the Directory File
// File Stream for "RandomStructDir.dir"
if ((fd2 = open("RandomStructDir.dir",O_RDWR|O_TRUNC)) != -1)
err_sys("Failed to open binary\n");
fillDirectoryStructs(countryDataPtr, director, nLines, fd2);
sortStructs(director, nLines); // Sorting the structs
// Write the number of lines in the FIRST LINE
// of the Directory File
write(fd2, nLines, sizeof(nLines));
// Writing Directory File after the number of lines was written
write(fd2,(director[0]->code[0]), sizeof(DirData));
close (fd2);
//fclose(Fout);
printf("RandomStructDir.dir written Sucessfully\n\n");
exit(0);
}
// Filling the Country structs
// - This function extracts the data from the file using strtok
// and fills all the structs with their corresponding values.
void fillCountryStructs(CountryData **dataPtr, int nLines, int fd)
{
int curLine = 0; // Current line
int index = 0; // The index
char buf[BUFSIZE]; // The Buffer with the size of BUFSIZE
char *tok; // Token
char *pEnd; // For the String functions
char ch = 'a'; // The temp character
int temPop;
double temLifeExp;
int num=0;
for(curLine = 0; curLine < nLines; curLine++)
{
// Reading each line
dataPtr[curLine] = (CountryData *)calloc(1, sizeof(CountryData));
index = 0;
do
{
read(fd, &ch, 1);
buf[index++] = ch;
}
while(ch != '\n');
// Strtoking...
tok = strtok(buf, ",\n");
index = 1;
while(tok != NULL)
{
tok = strtok(NULL, ",\n");
// Get the Country Code
if(index == 1)
{
strcpy(dataPtr[curLine]->code, tok); // Copying code to the struct
}
// Get the Country Name
if(index == 2)
{
strcpy(dataPtr[curLine]->name, tok); // Copying name to the struct
}
// Get the Country Population
if(index == 7)
{
temPop = (int)strtol(tok, &pEnd, 10);
dataPtr[curLine]->population = temPop; // Copying population to the struct
}
// Get the Country Life expectancy
if(index == 8)
{
num=countchar(tok);
printf ("The number of characters entered is %d\n", num);
printf ("The character entered is %s\n",tok);
temLifeExp = strtod(tok, &pEnd);
dataPtr[curLine]->lifeExp = temLifeExp; // Copying life expectancy to the struct
}
index++;
}
}
}
int countchar (char list[])
{
int i, count = 0;
for (i = 0; list[i] != '\0'; i++)
count++;
return (count);
}
// Filling the Directory Structs
// - This function fills the directory with the offset
void fillDirectoryStructs(CountryData **dataPtr, DirData **director, int nLines, int fd2)
{
int i = 0;
for(i = 0; i < nLines; i++)
{
strcpy(director[i]->code, dataPtr[i]->code); //It crashes in this Line
director[i]->offSet = sizeof(CountryData) * (i);
}
}
// Sorting the Dir Structs
// - This function sorts the Directory Structs.
void sortStructs(DirData **director, int nLines)
{
int maxNumber;
int i;
DirData **temp;
temp = calloc(1, sizeof(DirData));
// Sorting the array of pointers!
for(maxNumber = nLines - 1; maxNumber > 0; maxNumber--)
{
for(i = 0; i < maxNumber; i++)
{
if((verifyString(director[i]->code, director[i+1]->code)) == 1)
{
temp[0] = director[i];
director[i] = director[i+1];
director[i+1] = temp[0];
}
}
}
}
// Veryfying the strings
// - This function compares two strings and return a specific value
// accordingly.
int verifyString(char *s1, char *s2)
{
int i;
if(strcmp(s1,s2) == 0)
return(0); // They are equal
for(i = 0; s1[i] != 0; i++)
{
if(s1[i] > s2[i])
return(1); // s1 is greater
else if(s1[i] < s2[i])
return(2); // s2 is greater
}
return (2); // s2 is greater
}
So I get segmentation fault and I have no Idea why? maybe is something about the pointers. I specified where it crashes (void fillDirectoryStructs) that method the first line.
When I compile I get :
Countries.c: In function 'main':
Countries.c:68: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:84: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:86: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:232:2: warning: no newline at end of file
I don't know a lot about pointers but I have to use system calls, so I can't use any of the FILE * functions (fwrite(), etc) that is why I'm using plain write() and read().
When I run it I get segmentation fault when It gets to that point I just specified.
for test purposes I'm trying to print this
printf("test: %s\n", countryDataPtr[0]->code[0]);
instead of writing and it crashes there, why? what am I doing wrong? shouldn't that get the code of that first country in my struct? thanks
Well, you need to listen to your compiler and take its warnings seriously.
This:
write(fd2, nLines, sizeof(nLines));
is wrong, and would explain the warning. The variable nLines has type int, but if you look at the [documentation for write()] you can see that the 2nd argument has type void *.
So it will interpret your integer value as a pointer, and start reading memory which you have no right to be reading.
You need:
write(fd2, &nLines, sizeof nLines);
Note that sizeof is not a function, it only needs parenthesis when the argument is a type name (since it then needs a cast expression to the type in question, and casts are writen as a type name enclosed in parenthesis).
Also, you need to be prepared for the reality that I/O can fail. The write() function has a return value which you should be checking.
There are a number of other problems with your code, in addition to the serious one unwind pointed out.
This:
CountryData **countryDataPtr; // Array of structs
is not an Array of structs. Once allocated, it could be an array of pointers to structs.
This:
write(fd2, (countryDataPtr[0]->name[0]), sizeof(CountryData));
does not write one CountryData instance (much less a whole array of them). It takes the integer value of the first character of the first element's name, and treats it as a pointer just like you do with nLines.
If you want to write the first element, it would look like this:
write(fd2, countryDataPtr[0], sizeof(CountryData));
and if you wanted to write all the elements, you'd either need a loop, or a contiguous array of structs you can write in one go.

Resources