Passing a reference of a member in a struct to modify it - c

I'm doing a program for a university project. currently I'm doing the user configuration for being able to change both the password and the user name. I was thinking a way to use a single function for both process instead of creating two separated ones.
My goal was to pass a reference to the struct that holds the user data, and a reference to the corresponding member of the struct I want to edit, both being a char[64]. So after I edit the member I can just rewrite the whole line in the file with the updated data.
void change_user_data(char *input, struct user ***logged_user,
char *data_to_alter, FILE **user_registry) {
// making sure the returned input is valid.
if (strcmp(input, "..."))
return;
struct user find_user;
fseek(*user_registry, 0, SEEK_SET);
while (!feof(*user_registry)) {
fread(&find_user, sizeof(struct user), 1, *user_registry);
if (0 != strcmp(find_user.user_name, (**logged_user)->user_name))
continue;
strcpy(data_to_alter, input);
fseek(*user_registry, 0, SEEK_CUR - 1);
fwrite((**logged_user), sizeof(struct user), 1, *user_registry);
break;
}
return;
}
This is the function that should handle the change of both the struct and the file.
change_user_data(change_password(input), &logged_user, (*logged_user)->password, &user_id);
and here was my attempt to pass the arguments.
There was no error message, but there wasn't any change neither for the struct or the file. I guess that what is happening is that it's only creating a copy of the member, I tried to change the way I passed the member but none of them worked.
Minimal Reproductable Example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLEN 64
struct user {
char user_name[MAXLEN];
char password[MAXLEN];
};
void placeholder_function(struct user *logged_user);
void change_user_data(char *input, struct user **logged_user,
char *data_to_alter, FILE **user_registry);
int main() {
struct user logged_user;
placeholder_function(&logged_user);
return 0;
}
void placeholder_function(struct user *logged_user) {
FILE *user_registry;
if (!(user_registry = fopen("user_registry.bin", "w+b"))) {
printf("Couldn't open file\n");
exit(1);
}
strcpy(logged_user->user_name, "Admin\0");
strcpy(logged_user->password, "Admin\0");
fseek(user_registry, 0, SEEK_SET);
fwrite(logged_user, sizeof(struct user), 1, user_registry);
// expected output: Admin Admin
printf("%s %s\n", logged_user->user_name, logged_user->password);
// rest of program here...
change_user_data("1234\0", &logged_user, logged_user->password,
&user_registry);
printf("%s %s\n", logged_user->user_name, logged_user->password);
// wanted output: Admin 1234
}
void change_user_data(char *input, struct user **logged_user,
char *data_to_alter, FILE **user_registry) {
struct user find_user;
fseek(*user_registry, 0, SEEK_SET);
while (!feof(*user_registry)) {
fread(&find_user, sizeof(struct user), 1, *user_registry);
if (0 != strcmp(find_user.user_name, (*logged_user)->user_name))
continue;
strcpy(data_to_alter, input);
fseek(*user_registry, 0, SEEK_CUR - 1);
fwrite((*logged_user), sizeof(struct user), 1, *user_registry);
break;
}
}

I'm not 100 % sure what's going wrong in your specific example.
But you only have to pass the address of the struct or the address of the element, not both. In this case:
struct user ***logged_user
Then you can modify the element with:
strcpy((**logged_user)->password, input);
Now if you would only care about the password and not the username in this function, you could have passed in a pointer to only the password, and not the container struct. But that's your choice.
Now there are some other concerns with this code. You need to make sure the copy from input doesn't cause a buffer overrun in (**logged_user)->password. You can either do this by making sure the input is small enough, or using the strncpy function instead. Make sure destination becomes null terminated, typically with (**logger_user)->password[pass_len - 1] = '\0'
This line
fseek(*user_registry, 0, SEEK_CUR - 1);
Is weird, SEEK_CUR - 1 will evaluate to SEEK_SET, which I doubt you want to do.

Well, It works now. Just changed the edit of the struct to the function change_password(). And edited the exact member i wanted to edit.
bool change_password(char *new_password, struct user ***logged_user) {
char repeat_password[MAXLEN];
system(CLEAR);
printf("\nInput the new password: ");
trim_line(get_input(new_password, MAXLEN));
printf("\nRepeat the new password: ");
trim_line(get_input(repeat_password, MAXLEN));
if (0 != strcmp(new_password, repeat_password)) {
fprintf(stderr, "\nNew password mismatch!\n");
printf(KEY_PRESS);
free_buffer();
return false;
}
strcpy((**logged_user)->data.password, new_password);
return true;
}
After that I pass the struct to the change_user_data() function and done
void change_user_data(bool is_valid, struct user ***logged_user,
FILE **user_registry) {
// making sure the returned input is valid.
if (!is_valid)
return;
struct user find_user;
fseek(*user_registry, 0, SEEK_SET);
while (!feof(*user_registry)) {
fread(&find_user, sizeof(struct user), 1, *user_registry);
if (0 != strcmp(find_user.data.user_name, (**logged_user)->data.user_name))
continue;
fseek(*user_registry, 0, SEEK_CUR - 1);
fwrite((**logged_user), sizeof(struct user), 1, *user_registry);
break;
}
return;
}
I still need to implement the other advice I was given, like not using feof(), and placing myself correctly in the file, but it's a start.

Related

Trying gets(string) function in C but program crashes when I input key

I am building a Password Manager Program in C for a school assignment and at the moment of execute the following function, the program crashes.
I first call it on this case statement.
case 1:
// new record adding
s = Inputdata();
fwrite(&s, sizeof(password), 1, f);
sA[d] = s;
d++;
break;
then we got the function Inputdata() which will storage the string of the password
password Inputdata()
{
password s;
fflush(stdin); // clearing memory buffer
printf("\nEnter name of the site for the password (Ex. Gmail password)\t:\t");
gets(s.name);
printf("\nEnter unique password identifier. \t:\t");
scanf("%d", &s.ID);
printf("\nEnter password to create\t:\t");
gets(s.pw);
return s;
}
Here is the struct for the password
struct password
{
// Data Members
char name[30];
int ID; // password ID
char pw[30]; //storage of password
};
typedef struct password password;
Why is the program crashing, I'm not the best programmer at C, I come from Python and JavaScript so if I'm not parsing the data correctly I think this may be the thing.
Also here is the full code for the program, for references:
// password record Management using file /array/function
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
// structure of password details composite structure by using mark structure
struct password
{
// Data Members
char name[30];
int ID; // password ID
char pw[30]; //storage of password
};
typedef struct password password;
// input block
password Inputdata()
{
password s;
fflush(stdin); // clearing memory buffer
printf("\nEnter name of the site for the password (Ex. Gmail password)\t:\t");
gets(s.name);
printf("\nEnter unique password identifier. \t:\t");
scanf("%d", &s.ID);
printf("\nEnter password to create\t:\t");
gets(s.pw);
return s;
}
// output block
void showdata(password s)
{
printf("\n%s\t\t%d\t%s", s.name, s.ID, s.pw);
}
// find a password according to IDs
int findpass(password s, int i)
{
if (s.ID == i)
{
showdata(s);
return 1;
}
else
{
return 0;
}
}
// menu displaying
int menu()
{
int choice;
system("cls"); // for clearing screen
printf("\n|Password Manager - C2000 v1.0|\n");
printf("1. Add new password \n");
printf("2. Delete password \n");
printf("3. Find password\n");
printf("4. Show all the password \n");
printf("7. Showing Sorted IDs\n");
printf("0. Quit\n");
printf("Pick one : ");
scanf("%d", &choice);
return choice;
}
// deleting a record from array
void delData(password *s, int cur, int di)
{
int found = 0, i, j;
for (i = 0; i < cur; i++)
{
found = findpass(s[i], di);
if (found == 1)
break; // break statement outside the switch and used in loop
}
if (found == 0)
printf("No Record Found");
else
for (j = i; j < cur - 1; j++)
{
s[j] = s[j + 1];
}
}
password s; // global variable
// main driver program
int main()
{
// first of all making binary file
FILE *f = fopen("password1.dat", "wb+"); // file oprn
int id, pw, i = 0, j;
int d;
if (!f)
{
printf("Cannot open file!\n");
return 1;
}
printf("|Password Manager - C2000 v1.0|\n ");
printf("\nPress any key to start...\n");
// loading array from binary file
password *sA = (password *)malloc(d * sizeof(password)); // dynamic memory initialization of array
fseek(f, 0, SEEK_SET);
password k, temp;
for (i = 0; i < d; i++)
{
fread((char *)&sA[i], sizeof(s), 1, f); // loading array from file
showdata(sA[i]); // showing array
}
getch();
// loop will execute until not exit
while (1)
{
int ch = menu();
switch (ch)
{
case 1:
// new record adding
s = Inputdata();
fwrite(&s, sizeof(password), 1, f);
sA[d] = s;
d++;
break;
case 2: // delete from array
{
int di, found = 0;
printf("\n Enter ID to Delete\t:\t");
scanf("%d", &di);
delData(sA, d, di);
d--;
break;
}
case 3: // find password
{
int di, found = 0;
printf("\n Enter ID to Delete\t:\t");
scanf("%d", &di);
for (i = 0; i < d; i++)
{
found = findpass(sA[i], di);
}
if (found == 0)
printf("No Record Found");
break;
}
case 4: // print all
for (i = 0; i < d; i++)
{
fread((char *)&sA[i], sizeof(s), 1, f); // loading array from file
showdata(sA[i]);
}
getch();
break;
case 5:
// sorting of array and displaying
for (i = 0; i < d - 1; i++)
{
for (j = i + 1; j < d; j++)
{
if (sA[i].ID > sA[j].ID) // checking and swapping
{
temp = sA[i];
sA[i] = sA[j];
sA[j] = temp;
}
}
}
// showing sorted data on screen
for (i = 0; i < d; i++)
{
showdata(sA[i]);
}
break;
default:
exit(0);
}
getch();
}
free(sA);
}
As noted in the comments there a number of issues with your program. Hopefully list this captures them all:
conio.h is windows specific. Don't use get() or gets() as they are unsafe.
struct password { ...; char pw[30]; }. Your struct is really an account so you end up with a name clash between the name of the struct and the name of the pw attribute.
(Not fixed) struct password: name and password are fixed sized strings. It means you can create a record and write into those fields directly. On the flip side, it also means when you fwrite() the whole record you end up with a lot of padding.
Use constants instead of magic values (name length, password length, name of your password file, and version string).
Inputdata() different naming convention than all other functions.
Inputdata(): scanf("%d", ..)` leaves the trailing newline so you need to flush it before the next read (unless it happens to be another function that ignores leading white space.
showdata(): Prefer trailing to leading newlines to ensure the output buffer is flushed.
showdata(): Used to display 1 record, but 3 out 4 calls it is used to display all records. Make it generic to print accounts_len` records so it serves both use cases. Or have two functions one that shows one record and one that shows all (where the latter could call the former). I might use a macro for a thin wrapper function like that.
findpass(): accepts a password but in both use cases it runs in a loop over all accounts. It makes sense to have it operate over all accounts and return the index of the found account and -1 for failure.
findpass(): Is used multiple places so it's better that it just returns the result without writing it out.
findpass(): As you return when handling the first case; else {} only adds noise. This style is called early return.
menu(): system("cls") is windows specific. Implement a function (like I did), or leave it out as you lose important context when clearing the screen.
(Not fixed) menu() / main(): are tightly coupled "1" in menu matches a case in main(). It would be better if they derive from the same data, in particular, a struct that maps between the menu selector, what is being displayed, and the function to call. menu() could also be inlined in main(). Switching on an integer also makes it hard to read so I introduced an enum to use constants instead.
(Not fixed) menu() / main(): 1 for add is not great user experience, it would be better to use a for add,d for delete etc. It's also odd that the list is semi-ordered 1 through 4, then random 7 and 0.
menu(): scanf("%d", ..) will leave the newline in the buffer so need to flush it in case the next i/o operation doesn't deal with leading white space.
delData(): different naming convention than all other functions.
delData(): It changes the size of the array so it would make sense for it to modify di. Similar for array s. To change a value you need to pass the address in.
delData(): Instead of copying data in a loop like you do use memcpy(). It's highly optimized for this task. For instance, it might read a chunk of data and write a chunk data instead of per record. If you have a lot of data copy, however, becomes slow , if you don't care about the order of records you can overwrite the record being deleted with the last record. Another often used trick is to just mark a record as deleted. This is referred to as a tombstone. When you save your records you then just skip the tombstone. If you have lots of deletions you could also compact it once in a while. This would be referred to as a garbage collection.
password s: global variables are bad, it's particular bad because it's a single character so it's hard find when you need to fix it.
password s: It's the wrong type, as I think you were trying to use it for the array (sA) but it's a single struct.
main(): minimize scope of variables (declare just before you need them).
(main issue) main(): prefer to initialize variables. d is uninitialized so your whole program is undefined behavior. Even if it's 0, malloc(0) is not portable.
main(): fopen(..., "w") will truncate your password file.
main(): check that f is valid right after you set it.
main(): consecutive print statements can be combined. In this case the name and version thingy is in your menu for the first statement can be eliminated.
main(): Don't cast the void * from malloc(). It is not needed.
main(): You assume you have d passwords but you have no idea how many there is. If you can change format of the file, consider writing a count of records first. Then you might be able to fread() them all in one go.
main(): getch() having to hit enter to us the program would array me.
(minor) main(): for(;;) is shorter than while(1).
main() add: sA[d] = s you cannot assign one struct to another like this. Use memcpy, or assign/strcpy each field individually.
main() add: You prompt the user for an unique id, but you never check for that. It's bad user experience, program should perhaps just generate unique numbers, or don't use them at all and support find by different fields and handle more than 1 result for find.
main(): If scanf() fail di is uninitialized so this means any record may be deleted.
main() find: The prompt it wrong, it says "Delete".
`main() show: You re-read all records from the file again.
`main() sort: You sort the data in-place which is ok but you lose the original order. If you want a sorted array insert new records in sorted order instead of at the end. If you do that, this menu option just goes away.
main() sort: Use qsort() instead of writing a worse algorithm in a case statement.
`main() exit: any wrong key and your program exit. It's better to to ignore unexpected input and only exit when needed.
(Not fixed, minor) User interface is quite verbose with the menu being shown after every action. Consider only showing the menu when there is no password file, i.e. on first run, or run it once on start-up and then only when requested with h or ?. Besides just being verbose it obscures error messages, and data.
(Not fixed) add/delete: both manipulate size of the array. The realloc() part could be extracted and shared so you only deal with it once. It's relatively expensive (but much less so than fwrite/fwrite) so if combined you maintain a capacity in addition to len. Then grow capacity with more than 1 record a time, and then you don't have to do it as often. This includes the initial load, you might start with allocating 100 records, and then you may not need to ever resize it.
delData(): doesn't write out the new state. Probably because you found you had to rewrite parts of the file. This means those deleted records will come back from the dead if you display all records as it reloads the data from disk, or if you quit and start the program again. This a form of data corruption. It's a better design to strictly separate file i/o and add/delete.
(Not fixed) If something happens save(), say a power outage or your device runs out of battery, your file might become corrupted. It's better write to a new file, then when that is successful move the new file over the old file. It will either work or not. If not, you have the option of doing so on start-up. Now you need a way to detect if the file is corrupted, one way is via a check sum of the data. Or leave it as a manual task to inspect the old and new files before changing anything.
(Not fixed) Error handling could be improved. For instance, by only having the user interfacing functions generate errors. Now each lower level functions need a way to communicate what failed (error codes and/or error strings etc).
(Not fixed) Prompts are confusing. For instance when you ask for user site you give "Gmail password" as an example. It's a site or url not a password. Also it's a Google Account not a Gmail account. "Password to create", you are not really creating anything but recording the password in this system.
(Not fixed) You may want to reconsider your choice of opening the file in binary mode. If it was just a text file, then you have the option of using an editor to make a bunch of changes and till use your program to query it. If you use a tagged format, like json object, for instance, you can ignore fields you don't know about about. As discussed below this may allow for more gracefully updates.
(Not fixed) Consider adding a version to your data file. This gives you one branching point if you need to support multiple versions. Otherwise you have to detect the format based on the content, and it may it be ambiguous. For instance the value domain of a cleartext and an encrypted password may overlap.
It took me hours to rewrite your program so hopefully it's helpful.
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NAME_LEN 30
#define PASSWORD_LEN 30
#define PASSWORD_PATH "password1.dat"
#define VERSION "1.0"
typedef struct {
char name[NAME_LEN];
int ID;
char password[PASSWORD_LEN];
} account;
enum menu {
QUIT,
ADD,
DELETE,
FIND,
SHOW,
SORT = 7
};
int find(size_t accounts_len, const account *accounts, int id);
int add(size_t *accounts_len, account **accounts, const account *a) {
account *tmp = realloc(*accounts, (*accounts_len + 1) * sizeof(**accounts));
if(!tmp) {
printf("realloc failed\n");
return -1;
}
*accounts = tmp;
memcpy(&(*accounts)[*accounts_len], a, sizeof *a);
(*accounts_len)++;
return 0;
}
void clear() {
// Move cursor to top-left corner and clear the erase entire screen
fputs("\e[;1H\e[2J", stdout);
}
int compare(const void *a, const void *b) {
int a_id = ((const account *) a)->ID;
int b_id = ((const account *) b)->ID;
if(a_id < b_id) return -1;
if(a_id == b_id) return 0;
return 1;
}
int delete(size_t *accounts_len, account **accounts, int id) {
int i = find(*accounts_len, *accounts, id);
if(i == -1)
return -1;
if(i > 0)
memmove(&(*accounts)[i], &(*accounts)[i+1], (i - 1) * sizeof(**accounts));
account *tmp = realloc(*accounts, (*accounts_len - 1) * sizeof **accounts);
if(!tmp) {
printf("realloc failed\n");
// TODO: account is gone but was not able to shrink accounts
(*accounts_len)--;
return -1;
}
*accounts = tmp;
(*accounts_len)--;
return 0;
}
int find(size_t accounts_len, const account *accounts, int id) {
for (size_t i = 0; i < accounts_len; i++) {
if(accounts[i].ID == id) {
return i;
}
}
return -1;
}
int flush() {
for(;;) {
switch(getchar()) {
case EOF: return EOF;
case '\n': return 0;
}
}
}
int load(const char *path, FILE **f, size_t *accounts_len, account **accounts) {
*accounts_len = 0;
*accounts = NULL;
*f = fopen(path, "ab+");
if (!*f) {
printf("Cannot open file!\n");
return 1;
}
fseek(*f, 0, SEEK_SET);
*accounts_len = 0;
*accounts = NULL;
for(;;) {
account a;
if(fread(&a, sizeof(a), 1, *f) != 1) {
if(feof(*f))
break;
printf("fread failed\n");
return 1;
}
if(add(accounts_len, accounts, &a) == -1) {
printf("add failed\n");
return -1;
}
}
return 0;
}
int menu() {
//clear();
printf(
"|Password Manager - C2000 v" VERSION "|\n"
"1. Add new password \n"
"2. Delete password \n"
"3. Find password\n"
"4. Show all the password \n"
"7. Showing Sorted IDs\n"
"0. Quit\n"
"Pick one : "
);
int choice = getchar();
printf("\n");
if(flush() == EOF) return EOF;
return choice - '0';
}
int save(FILE **f, const size_t accounts_len, const account *accounts) {
*f = freopen(NULL, "wb", *f);
if(!*f) {
printf("freopen failed\n");
return 1;
}
if(fwrite(accounts, sizeof *accounts, accounts_len, *f) != accounts_len) {
printf("fwrite failed\n");
return 1;
}
fclose(*f);
return 0;
}
void show(size_t accounts_len, const account *accounts) {
for(size_t i = 0; i < accounts_len; i++)
printf("%s\t%d\t%s\n",
accounts[i].name,
accounts[i].ID,
accounts[i].password
);
if(accounts_len) printf("\n");
}
void strip(char *s) {
s[strcspn(s, "\n")] = '\0';
}
int main() {
FILE *f;
size_t accounts_len;
account *accounts;
if(load(PASSWORD_PATH, &f, &accounts_len, &accounts) == -1) {
printf("load failed\n");
return 1;
}
for(;;) {
switch(menu()) {
case ADD: {
account a;
printf("Enter name of the site for the password (Ex. Gmail password): ");
fgets(a.name, NAME_LEN, stdin);
strip(a.name);
printf("Enter unique password identifier: ");
if(scanf("%d", &a.ID) != 1) {
printf("scanf failed\n");
break;
}
if(flush() == EOF) return 0;
if(find(accounts_len, accounts, a.ID) != -1) {
printf("account with that id exist already\n");
break;
}
printf("Enter password to create: ");
fgets(a.password, PASSWORD_LEN, stdin);
strip(a.password);
if(add(&accounts_len, &accounts, &a) == -1) {
printf("add failed\n");
}
break;
}
case DELETE: {
int id;
printf("Enter ID: ");
if(scanf("%d", &id) != 1) {
printf("scanf failed\n");
break;
}
flush();
if(delete(&accounts_len, &accounts, id) == -1) {
printf("delete failed\n");
}
break;
}
case FIND: {
int id;
printf("Enter ID: ");
if(scanf("%d", &id) != 1) {
printf("scanf failed\n");
break;
}
flush();
int i = find(accounts_len, accounts, id);
if(i != -1) show(1, &accounts[i]);
break;
}
case SHOW:
show(accounts_len, (const account *) accounts);
break;
case SORT:
qsort(accounts, accounts_len, sizeof(*accounts), compare);
show(accounts_len, accounts);
break;
case EOF:
case QUIT:
goto out;
default:
printf("invalid menu choice\n");
break;
}
}
out:
if(save(&f, accounts_len, accounts)) {
printf("save failed\n");
return 1;
}
free(accounts);
return 0;
}
and here is example session:
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 4
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 1
Enter name of the site for the password (Ex. Gmail password): john
Enter unique password identifier: 1
Enter password to create: john
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 1
Enter name of the site for the password (Ex. Gmail password): jane
Enter unique password identifier: 0
Enter password to create: jane
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 4
john 1 john
jane 0 jane
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 7
jane 0 jane
john 1 john
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 0
|Password Manager - C2000 v1.0|
1. Add new password
2. Delete password
3. Find password
4. Show all the password
7. Showing Sorted IDs
0. Quit
Pick one : 4
jane 0 jane
john 1 john
It loads data on start-up and saves the file when the program exits. For debugging purposes it is convenient to kill the program with ctrl-c and explicit note save the file. You could save after every modification (add/delete) by moving the save() call. If you move to keeping the data sorted, your current incremental approach will no longer work.
Instead of writing your own file format seriously consider using SQLite or another database library. If you ever need change anything, say, add the record count that I mentioned above now you have to support two formats, or write a program to convert your file from one format to another. Remove ID field. Change the type of name and password to char *. Checksum at the end. Encrypt password. Write many, many programs. With SQLite those schema changes are much easier to write. You may even able to ignore fields you have deleted till you get around changing the schema. With your program you have to deal with it when you load the file.
I don't use windows so I it was not tested on your platform. Hopefully it works :-)

I can't process numbers in a file in C

When I execute this code, it works fine but the output in "kredi.txt" doesn't appear correctly. For example, when I enter 1 for "hesapNo", stone for "soyisim", white for "isim", 20.50 for "bakiye", the output in "kredi.txt" looks like this:
[ stone white ÌÌÌÌÌÌ €4#]
Why? This is my code:
#include<stdio.h>
#include<conio.h>
struct musteriVerisi
{
int hesapNo;
char soyisim[15];
char isim[15];
double bakiye;
};
int main() {
FILE *cfPtr;
struct musteriVerisi musteri = { 0," "," ",0.0 };
if ((cfPtr = fopen("kredi.txt", "r+")) == NULL)
printf("DosyaAcilamadi\n");
else {
printf("HesapNo girin:"
"(1 den 100 e kadar cikis icin 0 girin)");
scanf("%d", &musteri.hesapNo);
while (musteri.hesapNo != 0) {
printf("soyisim,isim,bakiye giriniz:\n?");
fscanf(stdin, "%s%s%lf", musteri.soyisim, musteri.isim, &musteri.bakiye);
fseek(cfPtr, (musteri.hesapNo - 1) * sizeof(struct musteriVerisi), SEEK_SET);
fwrite(&musteri, sizeof(struct musteriVerisi), 1, cfPtr);
printf("HesapNo girin:\n?");
scanf("%d", &musteri.hesapNo);
}
fclose(cfPtr);
}
getchar();
return 0;
}
The topic of the chapter containing this example in the book I study from is "Writing data to random-access file".
fwrite(&musteri, sizeof(struct musteriVerisi), 1, cfPtr);
That line of code makes no sense. You're just writing some binary garbage in memory to a file. There's no reason to expect that to make any sense when the program stops running -- who knows what internal format the system uses to store information.
For example:
char soyisim[15];
Say that contains a two character string with a terminating zero byte. What do the other 13 bytes contain? Do you know? I don't either. So why are you writing unknown garbage to a file?!

working with directories in POSIX with C

I will go ahead and say this is a homework assignment for an intro to Linux class. I would not be posting it without extensive attempts on my own, and seeing as I am a distance student this semester, I cannot make it to campus for tutoring. I need some help finding out what the issue is.
Essentially the assignment asks us to make a program that serves the same basic function as the pwd command in POSIX, to show the absolute path for the current directory. We are to use three functions along with main. We are not to use the getcwd command as well. I'll list them and their purpose
inum_to_filename: Accepts three arguments (inode number to translate, a pointer to a buffer where the name is written, and the size of the buffer). Returns nothing. It is to:
Open the current directory,
Read the first directory entry,
If the inode of the current directory matches the one passed in, copy name to buffer and return.
Otherwise read the next directory entry and repeat the previous step.
filename_to_inum: Accepts one argument (a char * representing the filename). It returns the corresponding inode number. It is to:
Read the information from the files inode into a structure in memory.
If there is any problem, display the appropriate error.
Return the inode number from the structure.
display_path: Accepts one argument (inode from the current working directory). It returns nothing. It is to:
Create an array of characters to use as a buffer for the name of the directory.
Get the inode for the parent directory using filename_to_inode.
If the parent inode is equal to the current inode, we have reached root and can return.
Otherwise, change to the parent directory and use inum_to_filename to find the name for the inode that was passed into the function. Use the buffer from step 1 to store it.
Recursively call display_path to display the absolute path.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
while (counter != 1) {
if (inode_arg == dirent_ptr->d_ino) {
strcat(pathBuffer, "/");
strcat(pathBuffer, dirent_ptr->d_name);
counter = counter + 1;
return;
} else {
dirent_ptr = readdir(dir_ptr);
}
}
closedir(dir_ptr);
}
int filename_to_inum (char *src) {
int res = 0;
struct stat info;
int result = stat(src, &info);
if (result != 0) {
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
} else {
res = info.st_ino;
}
return res;
}
void display_path (int ino_src) {
int bufSize = 4096;
char pathBuffer[bufSize];
int ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt) {
//print for test
inum_to_filename(ino_src, pathBuffer, bufSize);
printf("%s", pathBuffer);
return;
} else {
//print for test
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
}
int main (int argc, char *argv[]) {
int c_ino = filename_to_inum(".");
display_path(c_ino);
printf("\n");
}
As of right now it is displaying "/./MyName" with MyName being my personal named directory on the server. It is the directory I am running the program from. When using pwd I return "/home/MyName". I'm not really sure what my next step to getting the absolute path correct is.
The code is mostly set up to print one name at a time in the correct order, so the primary problem is the use of strcat() rather than strcpy(). Also, detecting when you're in the root directory at the start is important; if you don't, you can end up with /. or something similar (depending on exactly how you coordinate the printing) when the current directory is the root directory.
This version of your code has:
Squished the loop in inum_to_filename(), but also added error reporting. Remember, a process can be run in a directory which it does not have permission to get to (it requires a setuid program, usually — although permissions could be changed after the program is launched). In that case, it may fail to open .. (or .).
Lost variable count; it wasn't serving a useful purpose. Using the assign-and-test idiom allows the code to contain a single call to readdir().
Use strcpy() instead of strcat().
Use type ino_t to store inode numbers. Use size_t for sizes.
Reduce number of intermediate variables in filename_to_inum().
Note that the code in the if (ino_src == ino_prnt) statement body is for the root directory; in the absence of the testing print, it would do nothing.
Note that the printing in the else part is a major part of the operations, not just test printing.
Error check chdir("..");
Detect root in main().
Observe that this code is not directly suitable for rewriting into a function because it changes the process's current directory to / when it succeeds.
Revised code:
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static void inum_to_filename(ino_t inode_arg, char *pathBuffer, size_t size_arg)
{
assert(size_arg > 0);
DIR *dir_ptr = opendir(".");
if (dir_ptr == 0)
{
fprintf(stderr, "Failed to open directory '.' (%d: %s)\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
struct dirent *dirent_ptr;
while ((dirent_ptr = readdir(dir_ptr)) != 0)
{
if (inode_arg == dirent_ptr->d_ino)
{
if (strlen(dirent_ptr->d_name) >= size_arg)
{
fprintf(stderr, "File name %s too long (%zu vs %zu max)\n",
dirent_ptr->d_name, strlen(dirent_ptr->d_name), size_arg);
exit(EXIT_FAILURE);
}
strcpy(pathBuffer, dirent_ptr->d_name);
break;
}
}
closedir(dir_ptr);
}
static ino_t filename_to_inum(char *src)
{
struct stat info;
if (stat(src, &info) != 0)
{
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
}
return info.st_ino;
}
static void display_path(ino_t ino_src)
{
size_t bufSize = 4096;
char pathBuffer[bufSize];
ino_t ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt)
{
// print for test
inum_to_filename(ino_src, pathBuffer, bufSize);
printf("%s", "(root): /\n");
}
else
{
// print for real
if (chdir("..") != 0)
{
fprintf(stderr, "Failed to chdir to .. (%d: %s)\n",
errno, strerror(errno));
}
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("/%s", pathBuffer);
}
}
int main(void)
{
ino_t c_ino = filename_to_inum(".");
ino_t r_ino = filename_to_inum("/");
if (r_ino == c_ino)
putchar('/');
else
display_path(c_ino);
printf("\n");
}
There are undoubtedly other ways to fix this.
Caveat: this is giving me some grief when working in /Volumes/CRUZER/Sub-Directory which is a memory stick. It fails to find the inode (1, which is surprising) when scanning /Volumes, and I've not worked out why. One of my programs — a getpwd implementation — is working fine; another is having a different problem. I expect I'll get to the bottom of it all. Testing on Mac OS X 10.10.5 with GCC 5.1.0.
this is really nice assignment :).
I read and tried your code, and it is almost correct. There were two small issues which were causing the incorrect behaviour.
First issue
When display_path reaches the root folder you don't need to call inum_to_filename and print the name of the folder because you have already printed the first folder of the path in the previous iteration. This prevents your code from showing a "./" in the beginning of the path.
That is, the if condition becomes:
if (ino_src == ino_prnt) {
return;
} else {
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
Second Issue:
You're not initializing propertly the buffer where you save the name of the directory. This causes random values to be displayed. To solve this issue you can just set the initial value of the buffer to zero by using memset.
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
memset(pathBuffer, 0, size_arg);
while (counter != 1) {
...
}
closedir(dir_ptr);
}
Full code working :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
memset(pathBuffer, 0, size_arg);
while (counter != 1) {
if (inode_arg == dirent_ptr->d_ino) {
strcat(pathBuffer, "/");
strcat(pathBuffer, dirent_ptr->d_name);
counter = counter + 1;
return;
} else {
dirent_ptr = readdir(dir_ptr);
}
}
closedir(dir_ptr);
}
int filename_to_inum (char *src) {
int res = 0;
struct stat info;
int result = stat(src, &info);
if (result != 0) {
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
} else {
res = info.st_ino;
}
return res;
}
/*
- Create an array of characters to use as a buffer for the name of the directory.
- Get the inode for the parent directory using filename_to_inode.
- If the parent inode is equal to the current inode, we have reached root and can return.
- Otherwise, change to the parent directory and use inum_to_filename to find the name for
the inode that was passed into the function. Use the buffer from step 1 to store it.
- Recursively call display_path to display the absolute path.
*/
void display_path (int ino_src) {
int bufSize = 4096;
char pathBuffer[bufSize];
int ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt) {
return;
} else {
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
}
int main (int argc, char *argv[]) {
int c_ino = filename_to_inum(".");
display_path(c_ino);
printf("\n");
}
Output :
ubuntu#ubuntu-VirtualBox:~/dev$ vi pwd.c
ubuntu#ubuntu-VirtualBox:~/dev$ gcc pwd.c
ubuntu#ubuntu-VirtualBox:~/dev$ ./a.out
/home/ubuntu/dev
ubuntu#ubuntu-VirtualBox:~/dev$ pwd
/home/ubuntu/dev
ubuntu#ubuntu-VirtualBox:~/dev$

Binary file update in c....fwrite does not write the whole struct successfully

I have a file which is opened for both write/read (fopen(name,"wb+")) which contains a load of the struct below
struct pedia
{
int code;
char descr[50];
int TM;
int pos;
int flag;
};
The whole file is initialized with codes from 0 to the size of the file (user gives the size)flags equal to 0 ,descr=" " and TM=pos=-1
When i ask the user to write the # of the registration he wants to update and i print the struct which is saved there it s printed correctly.
Also when i call the input function in which the user sets new values for every variable in the struct i print the code,descr etc. right after and they are changed successfully .
However when i use fwrite to write the struct to the file it only writes 1 item successfully in the file.
void fileupdate(FILE *f,int filesize)
{
struct pedia *field;
field=(struct pedia *)malloc(sizeof(struct pedia));
int k,key;
char opt[5];
int num=0;
while(1)
{
puts("\nUPDATE\n");
printf("\nType the # of the registration you want to update (key must be between 0 and %d) \n\nkey:",filesize);
scanf("%d",&key);
getchar();
if(key>=0 && key<=filesize)
{
fseek(f,sizeof(struct pedia)*key,SEEK_SET);
fread(field,sizeof(struct pedia),1,f);
printf("%d,%s,%d,%d,%d\n",field->code,field->descr,field->TM,field->pos,field->flag);
if(field->flag==0)
{
puts("type yes to register new info or no to cancel the update\n");
fgets(opt,sizeof(char)*5,stdin);
if(isspace(*(opt+strlen(opt)-1)))
*(opt+strlen(opt)-1)='\0';
if(strcmp(opt,"yes"))
continue;
printmask();
input(&field);
num=fwrite(field,sizeof(struct pedia),1,f);
printf("\n%d,%s,%d,%d,%d\n",field->code,field->descr,field->TM,field->pos,field->flag);
printf("num=%d\n",num);
}
}
}
There is no fseek() between fread() and fwrite(), so fwrite() doesn't overwrite the struct you wanted to update but the next one.

C Programming - File - fwrite

I've got a question regarding programming and files.
while(current!=NULL)
{
if(current->Id_Doctor!='\0')
{
current=current->next;
id_doc=(current->Id_Doctor);
}
if(current->Id_Doctor=='\0')
{
id_doc=id_doc+1;
printf("%d", id_doc);
break;
}
}
fwrite(&id_doc, sizeof(char), 1, Archivo);
I dont know why but it aint writing the value of id_doc on the binary file called 'Archivo'...what could be the problem?
I added a printf of id_doc and the value was printed..I really dont know
Ok, heres the full code(more-less):
struct Medico
{
int Id_Doctor;
int Estado;
char Nombre[60];
char Clave_Acceso[20];
char Especialidad[40];
struct Medico *next;
};
void Dar_Alta_Med (int estado);
void MenuPrincipal(char enta);
int main(void)
{
char enta;
MenuPrincipal(enta);
}
void Dar_Alta_Med(int estado)
{
struct Medico * head = NULL;
struct Medico * prev, *current;
char nombre_doc[60], especialida[40], password[20];
int id_doc=0, estado_doc=1;
FILE *Archivo;
const char *md1="\n<md>\n";
const char *id_doc1="<id_doctor> ";
Archivo=fopen("md.dat", "ab+");
fwrite(md1, 1, strlen(md1), Archivo);
fwrite(id_doc1, 1, strlen(id_doc1), Archivo);
current = (struct Medico *) malloc (sizeof(struct Medico));
current->Id_Doctor=id_doc;
while(current!=NULL)
{
if(current->Id_Doctor!='\0')
{
current=current->next;
id_doc=(current->Id_Doctor);
}
else
{
id_doc=id_doc+1;
printf("%d", id_doc);
break;
}
}
fwrite(&id_doc, sizeof(id_doc), 1, Archivo);
printf("Ingresa el nombre del Doctor a dar de alta: ");
fclose(Archivo);
}
Im dying here, please help :/
Try adding fflush(Archivo); to force a write of all buffered data.
Also, this statement: if(current->Id_Doctor=='\0') really ought to be an else since there is no other thing it can be but '\0'
Three things:
Make sure your fopen is successful.
Archivo=fopen("md.dat", "ab+");
if (Archivo == NULL)
{
perror("Failed to open file Archivo");
...
}
Make sure you are checking the success of your fwrite's.
if (fwrite(&id_doc, sizeof(id_doc), 1, Archivo) < 1)
{
perror("Failed to write to file Archivo");
...
}
Make sure you have a fclose to close the file properly.
if (fclose(Archivo) != 0)
{
perror("Failed to close file Archivo");
...
}
Now that you've post a full sample of your code I guess I should ask if error checking is just left out for brevity? If not, you should think about adding it.
If you're expecting the value of id_doc to be in display format in the output file you'll have to convert the int to a string (using snprintf or similar) and write the string to the output file instead.
fwrite(&id_doc, sizeof(char), 1, Archivo);
If you defined id_doc as anything other than a char it will write \0 to the file.
Much cleaner would be:
fwrite(&id_doc, sizeof(id_doc), 1, Archivo);
If your first current is an Id_Doctor you have an endless loop.
If there is no current after your last current that is not an Id_Doctor, you get an illegal pointer derefenciation.
For your Problem:
try the flush() family.
You're passing a pointer to a FOUR-BYTE INT, but only writing ONE BYTE (the wrong byte)!
Solution: declare id_doc as "char", not "int".
You have previously written the strings "\n<md>\n" and"<id_doctor> " to the file Archivo, which seems to indicate that it is not a binary file at all, but rather an XML-style file.
In this case, what you almost certainly want is:
fprintf(Archivo, "%d", id_doc);

Resources