struct DVDInfo *ReadStruct( void ) {
struct DVDInfo *infoPtr;
int num;
char line[ kMaxLineLength ];
char *result;
infoPtr = malloc( sizeof( struct DVDInfo ) );
if ( NULL == infoPtr ) {
printf( "Out of memory!!! Goodbye!\n" );
exit( 0 );
}
printf( "Enter DVD Title: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->title = MallocAndCopy( line );
printf( "Enter DVD comment: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->comment = MallocAndCopy( line );
do {
printf( "Enter DVD Rating (1-10): " );
scanf( "%d", &num );
Flush();
}
while ( ( num < 1 ) || ( num > 10 ) );
infoPtr->rating = num;
printf( "\n----------\n" );
return( infoPtr );
}
What is the purpose of even having the variable "result" above? Nothing is done with it. The pointer returned from fgets is stored into it, but that is it, it has no purpose.
You should test that result for NULL, to check for an EOF condition or an error, instead of just ignoring it. Also, by not checking result, you are doing an strlen on line, which could have uninitialized data, because fgets failed. Really, you should have, after the fgets:
if (!result)
{
free(infoPtr); // To not leak the object allocated at the start
return NULL; // Function failed
}
You might still have leaks, if the first fgets succeeds and the second fails, because there are additional allocation to pointer members of the structure. Unfortunately, because the struct was not initialized to zero, you can't check those pointers for NULL. So, perhaps using calloc instead of malloc or at least initializing all structure pointer members to NULL, would have been a better idea.
It seems as though someone started to implement error checking, but botched it in the end. The return value should be compared with NULL, with an error reported if equal.
Most likely, the compiler threw a warning about a function return value that was ignored. The programmer didn't care about the return value of fgets and simply added in the result = to make the compiler quit nagging about it. The correct solution would be to check the return value to make sure the function completed successfully.
Related
I am very new to the C programming language and I am trying to make a login thing, the problem that I am having is that I can't get the user_len to compare in my while statement.
Thank you for any and all feedback
EDIT:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct User_name{
int user_id;
char username[20];
char password[30];
} User_data;
//make all the variable
int i;
int user_len;
int pass_len;
//prototype function
char user ();
int user_name();
int main() {
//get username
user_name (User_data.username);
}
int user_name() {
while(user_len > 20) {
printf("Enter your username:\n");
scanf("%s",User_data.username);
user_len = strlen(User_data.username);
if (user_len > 20) {
printf("\nusername is too long please enter again:%d\n",user_len);
} else {
printf("Username is: %s\n",User_data.username);
}
}
return(user_len);
}
The content of the while loop will never be executed, because the condition in the line
while(user_len > 20) {
will be false at the start of the program, because user_len will have the value 0.
Also, the lines
char user ();
int user_name();
are not prototype declarations. They are forward declarations of functions that take an unspecified number of arguments. If you want to declare that they take no arguments, then you should change them to:
char user( void );
int user_name( void );
See the following question for more information:
Warning/error "function declaration isn't a prototype"
The following code has a serious problem:
printf("Enter your username:\n");
scanf("%s",User_data.username);
user_len = strlen(User_data.username);
if (user_len > 20) {
printf("\nusername is too long please enter again:%d\n",user_len);
} else {
printf("Username is: %s\n",User_data.username);
}
There is no point in checking for a buffer overflow after the buffer overflow has already occurred. The buffer overflow must be prevented in the first place, in order to prevent your program from invoking undefined behavior (i.e. to prevent your program from possibly crashing).
The easiest way to prevent the buffer overflow would be to change the scanf format string from "%s" to "%19s", which would limit the number of matched characters to 19, so that it will not write more than 20 characters to User_data.username (including the terminating null character).
However, this solution is not ideal, as it is possible that scanf will leave non-whitespace characters from that line on the input stream (this is also possible when using "%s"). These non-whitespace characters will likely cause trouble in the next loop iteration when scanf is called.
For this reason, it would probably be best if you used the function fgets instead of scanf. The function fgets has the advantage that it will always read exactly one line at a time (provided that the supplied input buffer is large enough to store the whole line), which is not necessarily the case with scanf.
Here is my solution to the problem, which uses fgets instead of scanf:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct User_name
{
int user_id;
char username[20];
char password[30];
} User_data;
//make all the variable
int i;
int user_len;
int pass_len;
//prototype function
int user_name( void );
int main( void )
{
//get username
user_name();
}
int user_name( void )
{
while( true )
{
char line[100];
char *p;
//prompt user for input
printf( "Enter your username: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Error reading input!\n" );
exit( EXIT_FAILURE );
}
//find newline character
p = strchr( line, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( p == NULL )
{
//a missing newline character is ok on end-of-file condition
if ( !feof( stdin ) )
{
int c;
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Error reading from input\n" );
return false;
}
} while ( c != '\n' );
goto line_too_long;
}
}
else
{
//remove newline character by overwriting it with null character
*p = '\0';
}
//find length of string
user_len = strlen( line );
//check if length is acceptable
if ( user_len >= 20 )
{
goto line_too_long;
}
//username is ok, so copy it
strcpy( User_data.username, line );
//break out of infinite loop
break;
line_too_long:
printf( "Line was too long, try again!\n" );
}
//print username
printf( "Username is: %s\n", User_data.username );
return user_len;
}
This program has the following behavior:
Enter your username: ThisIsAVeryLongUserName
Line was too long, try again!
Enter your username: ShortUserName
Username is: ShortUserName
Note that this program uses one goto label and two goto statements. Normally it is a good idea to try to not use goto, but to use normal control flow statements instead. However, in this case, I believe that using goto was appropriate. The alternative would have been to duplicate the line printf( "Line was too long, try again!\n" ); and use a continue statement in two places in the program. I believe that it is better to handle this error in one place in the program (though I understand that this topic is highly controversial and that some people would consider it better to use code duplication).
Another issue worth mentioning is that you are using global variables (which I have taken over in my solution). This is considered bad programming style and should generally be avoided. Here is a modified version of my solution which avoids the use of global variables:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct user_data
{
int user_id;
char username[20];
char password[30];
};
//prototype function
int user_name( void );
int main( void )
{
//get username
user_name();
}
int user_name( void )
{
struct user_data ud;
int user_len;
while( true )
{
char line[100];
char *p;
//prompt user for input
printf( "Enter your username: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Error reading input!\n" );
exit( EXIT_FAILURE );
}
//find newline character
p = strchr( line, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( p == NULL )
{
//a missing newline character is ok on end-of-file condition
if ( !feof( stdin ) )
{
int c;
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Error reading from input\n" );
return false;
}
} while ( c != '\n' );
goto line_too_long;
}
}
else
{
//remove newline character by overwriting it with null character
*p = '\0';
}
//find length of string
user_len = strlen( line );
//check if length is acceptable
if ( user_len >= 20 )
{
goto line_too_long;
}
//username is ok, so copy it
strcpy( ud.username, line );
//break out of infinite loop
break;
line_too_long:
printf( "Line was too long, try again!\n" );
}
//print username
printf( "Username is: %s\n", ud.username );
return user_len;
}
So my assignment is to create a login system. Username and passwords will be checked with content in the “account.txt” file. The content is that file have structure look like this:
Account ID: 1
Name: John Lee
Pass: 7uf
Role: student
Account ID: 2
Name: Park Lee
Pass: 42h
Role: Lecturer
Here what i got so far:
struct Account {
char name[20];
char pass[20];
};
void Login (char name[], char pass[]){
FILE *sc;
struct Account acc;
sc = fopen("Account.txt","r");
fscanf(sc,"\nName: %s",acc.name);
fscanf(sc,"\nPass: %s",acc.pass);
if(strcmp(name,acc.name) == 0 && strcmp(pass,acc.pass)) {
printf("Login successful");
}
else {
printf("Name or Pass incorrect");
}
fclose(sc);
}
int main () {
struct Account log[20];
fflush(stdin);
printf("\n\t\tEnter your name: ");
gets(log[20].name);
printf("\t\tEnter your password: ");
gets(log[20].pass);
Login(log[20].name,log[20].pass);
}
return 0; }
What do you guys think i should do ?
in the function: login() the code needs to check every entry in the file before declaring a failure. After all, the first entry might not be for the person trying to login
regarding:
sc = fopen("Account.txt","r");
fscanf(sc,"\nName: %s",acc.name);
1) always check (!=NULL) the returned value from fopen() to assure the operation was successful.
2) need to move past the first line of each entry in the input file before trying to read the name
3) when using the input format specifiers '%s' and/or '%[...]' always include a MAX CHARACTERS modifier that is 1 less than the length of the input buffer because those specifiers always append a NUL byte to the input. This avoids a buffer overflow and the resulting undefined behavior.
I.E.
if( !sc )
{
perror( "fopen failed" );
exit( EXIT_FAILURE );
}
{input first line of acct and discard}
if( fscanf(sc,"\nName: %19s",acc.name) != 1 )
{
// handle error
}
However, if those lines in the input file contains those labels, like Name: Then the code needs to also input and discard those labels, as in the above example.
This seems to be homework, so I'm very reluctant to just 'give' you appropriate code. I would expect your instructor or TA would be able to help you with the details of what the code should be doing.
regarding statements like:
gets(log[20].name);
1) gets() is no longer part of the C language, Your compiler should have told you this.
2) the valid index into an array has the range: 0...(number of entries in array -1). So index 20 is beyond the end of the range. Suggest just using a pointer to the array.
3) Suggest using `fgets() to input each line from the file.
4) the struct you have declared will not work well with the actual data from the input file.
Suggest using:
#define MAX_LOG_ENTRIES 20
int main( void )
{
struct Account acc[ MAX_LOG_ENTRIES ] = { "","" };
char dummy[128];
size_t i;
for( i = 0; i<MAX_LOG_ENTRIES; i++ )
{
if( i< MAX_LOG_ENTRIES && fgets( dummy, sizeof( dummy ), sc ) )
{ // then successfully read 'account' line
if( fgets( dummy, sizeof( dummy ), sc ) )
{ // then successfully read 'Name:` line
// remove trailing newline
dummy[ strcspn( dummy, "\n" )] = '\0';
// skip past Name: ' label
char * namePtr = strchr( dummy, ':' );
if( namePtr )
{ // then found the ':'
// step by ': '
namePtr += 2;
}
// extract name
strcpy( log[i].name, namePtr );
if( fgets( dummy, sizeof( dummy ), sc ) )
{ // then successfully read 'Pswd:` line
// remove trailing newline
dummy[ strcspn( dummy, "\n" )] = '\0';
// skip past Pswd: ' label
char * pswdPtr = strchr( dummy, ':' );
if( pswdPtr )
{ // then found the ':'
// step by ': '
pswdPtr += 2;
}
// extract password
strcpy( log[i].pswd, pswdPtr );
// read/discard unused data line
fgets( dummy, sizeof( dummy ), sc );
// read/discard unused blank line
fgets( dummy, sizeof( dummy ), sc );
}
When the above for() loop exits, all the records are read into the array named log[] and the variable 'i' contains the number of entries in the array 'log[]' that are actually used
now the code needs to input the two fields from the user (name and pswd)
Then loop through the array log[] to see if there is a 'name+pswd' match.
if fgets( dummy, sizeof( dummy ), sc );a match is found, then success, otherwise the user failed to enter valid data.
Note: The above code fails to check for errors and similar problems, including if the input file contains less than 20 entries. You should be able to add the error (and EOF) checking
So let's say that a file has multiply lines each containing one word. I want to store the characters of every word in every line in a array. The code below clearly doesn't work because the -i is zeroed in every loop and the program starts storing characters in the 1st position of the array again. The code is:
while(1)
{
if(fgets(str, 50, fp) == NULL)
break;
for(i=0; i<strlen(str); i++)
p[i] = str[i];
}
you have separate counter variable for p and keep incrementing it to avoid
overwritting, like below.
int write_position = 0;
while(1)
{
if(fgets(str, 50, fp) == NULL)
break;
for(i=0; i<strlen(str); i++)
p[write_position++] = str[i]; // you will not lose previous ones here
}
at the end lenght of the array p is equal to write_position
Fix your file reading loop like this;
while (fgets(str, sizeof(yourString), fp) != NULL)
{
yourString[strlen(yourString) - 1] = '\0'; // delete the new line
printf("%s\n", yourString);
}
So simply in the above code, your while loop will be working until there is no another line to read in the file. In each turn of while loop, it will take one line from your file, and add it to your yourString char array. Notice that, fgets() will be taking newline characters too (\n) for every line in the file, so that we need to remove this characters from the array before we add another line in it.
Before the while loop, You need to declare a char array to store each line in it such as;
char yourString[stringSize];
You need to determine a stringSize for your array to make it has enough storage for your file.
the following proposed code snippet
performs the desired functionality
assumes that the array 'p[]' is an array of char pointers to pointers and contains (at least) enough entries to hold all the lines in the input file
no line in the input file is longer than 48 characters + newline
And now the proposed code:
#define MAX_LINE_LEN 50
char **p;
p = calloc( NUM_LINES_IN_FILE, sizeof( char * ) );
if( !p )
{
perror( "calloc failed" );
exit( EXIT_FAILURE );
}
// implied else, calloc successful
FILE *fp;
if( !(fp = fopen ( "inputFileName", "r" )) )
{
perror( "fopen failed" );
exit( EXIT_FAILURE );
}
// implied else, fopen successful
char str[ MAX_LINE_LEN ];
for( int i=0; fgets(str, sizeof( str ), fp); i++ )
{
// remove trailing newline char
str[ strcspn( str, '\n' ) ] = '\0';
p[i] = strdup( str );
}
fclose( fp );
I have the following snippet of code:
char *buffer2 = malloc(1024*sizeof(char));
scanf("%s",buffer2);
char *command = strtok(buffer2," ");
if (strcmp(command,"INFO") == 0)
{
char *file_path = strtok(NULL," ");
if (file_path != NULL)
{
info(file_path);
}
}
My goal is : given a string like "CMD_NAME ARG1 ARG2" ... to tokenize the string according this format. So far so good, I have written the above code, it compiles, the program does not crash ,but the outcome is ,however, wrong in the sense that if I input the string "INFO work/file1.sf" the command is parsed properly but for the file_path the strtok function returns a NULL pointer. I have been struggling with this for hours , I have read the documentation for this function, I have searched other responses on this subject ,but none of the them were satisfactory. If you have a clue of what I'm doing wrong please help me.
Given the following code:
char *buffer2 = malloc(1024*sizeof(char));
scanf("%s",buffer2);
char *command = strtok(buffer2," ");
if (strcmp(command,"INFO") == 0)
{
char *file_path = strtok(NULL," ");
if (file_path != NULL)
{
info(file_path);
}
}
the expression 'sizeof(char)' is defined by the standard as 1.
Multiplying anything by 1 will not change its' value
and has no effect on the parameter passed to malloc().
However, it does clutter the code, making understanding, debugging, and maintenance more difficult.
Suggest removing that expression.
when calling scanf()
always check the returned value to assure the operation was successful.
when using the "%s" format specifier..
must use a max input characters modifier, so the user cannot overrun the input buffer2
the "%s" format specifier will stop inputting when encountering any 'white space'
The call to strtok() will not find anything but the whole input line.
because the input will have stopped before inputting any spaces
because a space is one of the 'white space' characters.
Suggest the following code:
#define BUFFER_LEN (1024)
char *buffer2 = NULL;
if( NULL == (buffer2 = malloc( BUFFER_LEN ) ) )
{ // then malloc failed
perror( "malloc for 1024 bytes failed")
exit( EXIT_FAILURE );
}
// implied else, malloc successful
if( !fgets( buffer2, BUFFER_LEN, stdin ) )
{// then, fgets failed
perror( "fgets from stdin failed");
exit( EXIT_FAILURE )''
}
// implied else, fgets successful
char *command = NULL;
if( NULL != (command = strtok(buffer2," \n") ) )
{
if (strcmp(command,"INFO") == 0)
{
char *file_path = NULL;
if( NULL != (file_path = strtok(NULL," \n") ) )
{
info(file_path);
}
}
}
struct DVDInfo *ReadStruct( void ) {
struct DVDInfo *infoPtr;
int num;
char line[ kMaxLineLength ];
char *result;
infoPtr = malloc( sizeof( struct DVDInfo ) );
if ( NULL == infoPtr ) {
printf( "Out of memory!!! Goodbye!\n" );
exit( 0 );
}
printf( "Enter DVD Title: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->title = MallocAndCopy( line );
printf( "Enter DVD comment: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->comment = MallocAndCopy( line );
do {
printf( "Enter DVD Rating (1-10): " );
scanf( "%d", &num );
Flush();
}
while ( ( num < 1 ) || ( num > 10 ) );
infoPtr->rating = num;
printf( "\n----------\n" );
return( infoPtr );
}
I asked a different question about this code in another thread on stackoverflow but didn't want to double up on that one - why is the terminating zero being added to the end of these files read in by fgets? fgets adds the terminating zero anyway, isn't this overkill?
Generally, you replace the newline character that fgets adds to the string with a NUL character. In all cases, fgets will NUL-terminate.
See: http://www.opengroup.org/onlinepubs/009695399/functions/fgets.html
fgets writes a nul terminator into the buffer you provide (if you specify the buffer size as larger than 0). Otherwise you could not call strlen() on it, strlen() expects a string, and if it isn't nul terminated it is not a string.
You're asking about
line[ strlen( line ) - 1 ] = '\0';
This strips off the last character in line .If you've read a line, it replaces the last character, presumably a \n with a nul terminator.
Consider that fgets just read a line, e.g. your line buffer now contains the string "Hello\n" (the \n is just the escape sequence here, it's actually just 1 character, not 2)
strlen ("Hello\n") is 6, and 6-1 is 5, so the 5. index is replaced by 0
"Hello\n"
^
|
Add 0 terminator
Result:
"Hello"
Just be careful:
you don't want to do line[ strlen(line) - 1 ] = '\0'; on an empty string, in that case you'll end up doing line[-1].
You should check if fgets succeds. You don't want to poke around in line if fgets failed, and didn't write anything to your buffer.
You might want to check whether a whole line actually got read. IF the line you read is larger than
kMaxLineLength ,or e.g. if the last "line" in the file doesn't have a trailing \n , strlen(line) -1 will not be a \n (newline).
Your
result = fgets( line, kMaxLineLength, stdin );
is Ok since the size of line is kMaxLineLength.
fgets reads in at most one less than size characters from stream and stores them into the buffer ...
The line[ strlen( line ) - 1 ] = '\0'; are unnecessary (and insecure — strlen() isn't going to work properly if the string isn't already nul-terminated). fgets() will nul-terminate the buffer. Also, you should be checking that result != NULL before attempting to copy line. fgets() returns NULL at end-of-file or if an error occurs.
Yes, it's overkill.
One suggestion to make it more robust against code rot... change
result = fgets( line, kMaxLineLength, stdin );
to
result = fgets( line, sizeof(line), stdin );