0

I am having trouble using fscanf and would appreciate any help. I am trying to read from a file that I am in control of. Since I knew the format this is what I tried to do reading from that file.

typedef struct {
    char *website;
    char *user;
    char *password;
    char *description;
} Account;

typedef struct Database Database;

struct Database { 
    int number_of_acc;
    Account **accounts;
};



int 
load_database(Database *db, FILE *fp) {
    char website[MAX_BUFFER_SIZE], username[MAX_BUFFER_SIZE], password[MAX_BUFFER_SIZE];
    Account acc;

    if (db->number_of_acc == 0)
        db->accounts = malloc(5*sizeof(acc));

    while(fscanf(fp, "%s: %s, %s", website, username, password ) == 3) {

        if(db->number_of_acc > 5) 
            db->accounts = malloc(2*db->number_of_acc*sizeof(acc));

        acc.website = website;
        acc.user = username;
        acc.password = password;
        //acc.description = description;
        db->accounts = malloc(sizeof(acc));
        db->number_of_acc += 1;
    }

    return Success;
}

However, when going through gdb fscanf is not returning 3 so the while loop is ignored. Here is the input file.

Reddit: Username, Password

Any advice and help in terms of my code are greatly appreciated. Thank you for your time.

  • 1
    The second `%s` reads up to a white space character; the next character is, by definition, not a comma. You presumably need to use a (negative) scan set: `%[^,]` to read up to but not including a comma. You should avoid buffer overflows by specifying sizes for both `%s` and `%[…]` — see [How to prevent `scanf()` causing a buffer overflow in C?](https://stackoverflow.com/q/1621394/15168) – Jonathan Leffler Jun 07 '21 at 18:46
  • I am not sure I follow what your intent is here: `if(db->number_of_acc == 5) db->accounts = malloc(2*db->number_of_acc*sizeof(acc));` ? – ryyker Jun 07 '21 at 18:52
  • @ryyker I have since figured out my fscanf problem thanks to Johnathan. What that line of code is intending to increase the allocation size in case there are more than 5 accounts. I know it is not going to do it again after 5 account structs but I had it there for testing. – TheStudentProgrammer Jun 07 '21 at 18:58
  • @JonathanLeffler Thank you for the comment I have since fixed my fscanf issue. Is there a way to credit you for the help? – TheStudentProgrammer Jun 07 '21 at 18:58
  • I've copied my comment into an answer, which you could accept. – Jonathan Leffler Jun 07 '21 at 19:01
  • Accept symbol is hollow checkmark beside @JonathanLeffler answer. One other suggestion is to read the file once to get the number of lines in the file (lots written about how to do that in C), allocate the necessary memory base on number of records (lines), finally reset the file pointer to beginning of file, and read each line in and make you assignments into members of struct array. – ryyker Jun 07 '21 at 19:04
  • @ryyker thank you for the suggestion. I will continue the project with that idea. Counting the number of lines is a brilliant idea. Thank you. – TheStudentProgrammer Jun 07 '21 at 19:31

1 Answers1

2

Transferring a comment into an answer.

Problem with fscanf()

The second %s reads up to a white space character; the next character is, by definition, not a comma. You presumably need to use a (negative) scan set: %[^,] to read up to but not including a comma. You should avoid buffer overflows by specifying sizes for both %s and %[…] — see How to prevent scanf() causing a buffer overflow in C?

Problem with memory management

Note that as ryyker pointed out, you have memory management problems too. You need to check that your memory allocations succeeded, and the second one should be using realloc(), not malloc() (to avoid memory leaks and the loss of already entered data). Error checking still omitted — it is best to assign the result of realloc() to a new variable so that you don't lose the pointer to the previous allocation if the reallocation fails.

if (db->number_of_acc == 0)
{
    db->accounts = malloc(5*sizeof(acc));
    db->number_of_acc = 5;
}

and

if (db->number_of_acc >= db->number_of_acc)
{
    db->accounts = realloc(db->accounts, 2*db->number_of_acc*sizeof(acc));
    db->number_of_acc *= 2;
}

You need to consider whether the relational operator should be >= or >.

Also, the paragraph copying the data that was read is bogus:

acc.website = website;
acc.user = username;
acc.password = password;
//acc.description = description;
db->accounts = malloc(sizeof(acc));
db->number_of_acc += 1;

You don't show the structure definition, but if it contains arrays, you need to use strcpy() and if it contains pointers, you need to use strdup() or an equivalent function. You also need to assign to the right element of the array, and copy the data into the allocated space.

I'm not about to fix this — the question is incomplete on this topic and hence can't be answered definitively. On the face of it, you have an array of structures; you should copy the newly entered data into the next available structure. You need separate counters for the number of allocated structures in the array and the number that are in use (or, equivalently, the index of the next entry to use).

If you still have problems, ask a new question, but read about how to create an MCVE (Minimal, Complete, Verifiable Example — or MRE or whatever name SO now uses) or an SSCCE (Short, Self-Contained, Correct Example) — the same idea by a different name.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • I have now included the structure in the code block above. I apologize for any confusion. As for the string assignments, I will make the change now. Thank you again for your comments it was comprehensible and I appreciate the effort put in. – TheStudentProgrammer Jun 07 '21 at 19:26
  • With the structure shown (all members are pointers), you will need to use the POSIX [`strdup()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strdup.html) function (or an equivalent function) to copy the answers from the local arrays because they will go out of bounds when the function returns, and because each answer will be over-written with subsequent data entries. You should set the description to a null pointer — or some other determinate value. – Jonathan Leffler Jun 07 '21 at 19:29
  • ...or, you can determine what the maximum record field widths are for each of the items you will read in from the database, and define the `struct` members as eg. `char website[max_field_width_web_address];, char user[nax_username_width];, etc, etc, etc.` This makes it much less complicated to clean up, and less burdensome in all the ways dynamically allocating memory is burdensome. – ryyker Jun 07 '21 at 19:40
  • 1
    @ryyker — yup, and that size had better be no bigger than `MAX_BUFFER_SIZE - 1` given the arrays used to store the input data before stashing it into the structure. There are pros and cons to both solutions, depending in part on how variable the data is. Is the normal length shortish (say 16 or 32 bytes) yet the maximum can be huge (say 256 bytes)? If so, dynamic allocation may save space, which might be a factor (but probably isn't for a classroom exercise). If the maximum is shortish too (say 64 bytes), then the fixed-size array elements inside the structure are easier to handle. – Jonathan Leffler Jun 07 '21 at 19:44