1

I tried to delete a file and then rename the temporary file in the name of the deleted file from a function that did not work. Please help me

boolean delete_user(char user_name[256])   //only for Admin
{
    boolean status = FALSE;//what does the function return
    User_Data *ud = NULL;
    boolean found_user = FALSE;
    FILE *new_file = NULL;
    FILE *fp = NULL;
    char user_name_to_copy[256];
    char password_to_copy[256];
    char old_file_name[256];

    ud = find_user(user_name);
    if (ud == NULL) {
        printf("The username wasn't found!\n");
        return FALSE;
    }
    if (!strcmp(ud->permission_type, "Admin")) {
        printf("Cant delete an admin.");
        return FALSE;
    } else {
        // the user to delete was found 
        new_file = fopen("duplicate.txt", "wt");
        strcpy(old_file_name, ud->permission_type);
        strcat(old_file_name, "s.txt"); //the name of the file is in plural and ends with .txt
        fp = fopen(old_file_name, "rt");
        while (!feof(fp)) {
            //copy all the users except the user to delete the new file 
            fscanf(fp, "%s %s\n", user_name_to_copy, password_to_copy);
            if (strcmp(user_name_to_copy, user_name)) {
                fprintf(new_file, "%s %s\n", user_name_to_copy, password_to_copy);
            }
        }
        fclose(fp);
        fclose(new_file);
        printf(" %d ", remove(old_file_name));
        rename("duplicate.txt", old_file_name);
        remove("duplicate.txt");
        return TRUE;
    }
}

This function does not work when I call it from another function, but from the main the function works just fine.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 2
    Have you stepped-through your code with a debugger? – Dai Jan 29 '17 at 06:37
  • 2
    Show us how do you invoke the function from **main** and how do you invoke from **another function** – iwaduarte Jan 29 '17 at 06:40
  • 2
    See [`while (!feof(file))` is always wrong](http://stackoverflow.com/questions/5431941/while-feof-file-is-always-wrong) for one issue. It's unlikely to be the cause of the main problem you're reporting, but you should not indulge in bad habits even if they're not yet causing you pain. – Jonathan Leffler Jan 29 '17 at 06:40
  • 1
    You don't check that the `fopen()` calls work; you don't check whether `rename()` or `remove()` work. Those are all potential problems. If the unchecked `fopen()` calls fail, your program will probably crash. If the `rename()` or `remove()` calls fail, you'll be none the wiser — but you won't have done what you intended. Note that if the `rename()` succeeds, the `remove()` will fail. You should only invoke the `remove()` if the `rename()` fails…maybe, if that's the desired behaviour. – Jonathan Leffler Jan 29 '17 at 06:43
  • 1
    I see you're printing the return value from `remove()`. What does it print? – Schwern Jan 29 '17 at 07:26
  • Well, think twice... you try to delete a file after renaming it to something else. The file has been renamed, so remove is supposed not to find the file with the name it had before being renamed. – Luis Colorado Jan 30 '17 at 06:02
  • @user2301514: you can accept one of the answers by clicking on the grey checkmark below its score. – chqrlie Feb 04 '17 at 22:16

2 Answers2

2

There are multiple problems in your code:

  • You do not check if fopen() successfully opened the files.

  • You do not prevent potential buffer overflow when concatenating strings to compute the permissions file.

  • You should pass the array sizes to fscanf to prevent potential buffer overflows.

  • You should output meaningful and informative error messages, with the cause of failure, using strerror(errno).

  • The loop to copy the permission file is incorrect: Why is “while ( !feof (file) )” always wrong?

  • You remove the duplicate file even if you failed to rename it but managed to remove the original file: both files may be lost. Conversely, if the rename operation succeeded, removing the duplicate file is redundant.

Here is how you could improve the code:

#include <errno.h>
#include <string.h>

boolean delete_user(char user_name[256]) {  //only for Admin
    boolean status = FALSE; // the function return value
    User_Data *ud = NULL;
    boolean found_user = FALSE;
    FILE *new_file = NULL;
    FILE *fp = NULL;
    char user_name_to_copy[256];
    char password_to_copy[256];
    char old_file_name[256];

    ud = find_user(user_name);
    if (ud == NULL) {
        printf("User '%s' was not found!\n", user_name);
        return FALSE;
    }
    if (!strcmp(ud->permission_type, "Admin")) {
        printf("Cannot delete user '%s', user has admin status.\n", user_name);
        return FALSE;
    }
    // the user to delete was found 
    new_file = fopen("duplicate.txt", "wt");
    if (new_file == NULL) {
        printf("Cannot open file 'duplicate.txt': %s\n"
               strerror(errno));
        return FALSE;
    }
    // the name of the file is in plural and ends with .txt
    snprintf(old_file_name, sizeof old_file_name, "%ss.txt", ud->permission_type);
    fp = fopen(old_file_name, "rt");
    if (fp == NULL) {
        printf("Cannot open user file '%s': %s\n"
               old_file_name, strerror(errno));
        return FALSE;
    }
    // copy all the users except the user to delete the new file 
    while (fscanf(fp, "%255s %255s\n", user_name_to_copy, password_to_copy) == 2) {
        if (strcmp(user_name_to_copy, user_name)) {
            fprintf(new_file, "%s %s\n", user_name_to_copy, password_to_copy);
        }
    }
    fclose(fp);
    fclose(new_file);
    if (remove(old_file_name)) {
        printf("Error removing file '%s': %s\n",
               old_file_name, strerror(errno));
        remove("duplicate.txt");
        return FALSE;
    }
    if (rename("duplicate.txt", old_file_name)) {
        printf("Error renaming file 'duplicate.txt' to '%s': %s\n",
                old_file_name, strerror(errno));
        // keep duplicate.txt
        return FALSE;
    }
    // duplicates.txt was successfully renamed, no need to remove it.
    return TRUE;
}

Notes:

  • You create the temporary file in the current directory, which might reside on a different drive from the permissions file. rename() may fail to move the duplicate file from the current directory to that directory. Running the code with proper diagnostics should if this is the reason for failure.
Community
  • 1
  • 1
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Is it still necessary to `remove(old_file_name)`? – Schwern Jan 29 '17 at 07:42
  • @Schwern: yes it is: ***C11 7.21.4.2** ...If a file named by the string pointed to by new exists prior to the call to the rename function, the behavior is implementation-defined.* – chqrlie Jan 29 '17 at 07:44
  • Crazy! [POSIX seems to agree with me](http://pubs.opengroup.org/onlinepubs/009695399/functions/rename.html). *If the link named by the new argument exists, it shall be removed and old renamed to new.* – Schwern Jan 29 '17 at 07:47
  • @Schwern: Not every system is a POSIX system... the files are opened in text mode: this indicates the user might be running Windows, which might not be 100% POSIX compliant. – chqrlie Jan 29 '17 at 07:52
1
    printf(" %d ", remove(old_file_name));
    rename("duplicate.txt", old_file_name);
    remove("duplicate.txt");

This code is mostly redundant. There's no need to remove old_file_name, rename will blow over it. (Turns out this is a POSIX thing, the C standard does not guarantee it). There's no need to remove duplicate.txt, it's been renamed.

    if( remove(old_file_name) != 0 ) {
        fprintf( stderr, "Could not remove %s: %s", old_file_name, strerror(errno) );
    }

    if( rename("duplicate.txt", old_file_name) != 0 ) {
        fprintf( stderr, "Could not rename %s to %s: %s", "duplicate.txt", old_file_name, strerror(errno) );
    }

You need to do this checking every file operation. That includes fopen and fscanf.

Also, you might have the files backwards. It's rename( old, new ) but it really looks like you wrote rename( new, old ). If you don't have them reversed, consider variable names that make the code easier to understand.


    fp = fopen(old_file_name, "rt");
    while (!feof(fp)) {
        //copy all the users except the user to delete the new file 
        fscanf(fp, "%s %s\n", user_name_to_copy, password_to_copy);
        if (strcmp(user_name_to_copy, user_name)) {
            fprintf(new_file, "%s %s\n", user_name_to_copy, password_to_copy);
        }
    }

This code is problematic. As mentioned, it fails to check whether fopen and fscanf succeed. Using fscanf is a problem, if it fails to match it doesn't advance the file pointer. You can read the same line over and over again, failing each time. In general, avoid fscanf and scanf.

Instead, read the whole line with fgets and parse it with sscanf. Be sure to check whether sscanf succeeded, else you'll print gibberish. Note that sscanf has bounds on how big a string it will read to protect against buffer overflow.

    FILE *fp = open_file(old_file_name, "rt");
    char line[1024];
    while (fgets(line, 1024, fp)) {
        //copy all the users except the user to delete the new file 
        if( sscanf(fp, "%255s %255s\n", user_name_to_copy, password_to_copy) != 2 ) {
            fprintf(stderr, "Couldn't understand line '%s'\n", line);
            continue;
        }
        if (strcmp(user_name_to_copy, user_name)) {
            fprintf(new_file, "%s %s\n", user_name_to_copy, password_to_copy);
        }
    }
    fclose(fp);

Since checking whether a file opened or not gets redundant, I recommend writing a little function to do that.

FILE *open_file(char *filename, char *mode) {
    FILE *fp = fopen(filename, mode);
    if( fp == NULL ) {
        fprintf(
            stderr, "Could not open '%s' for '%s': %s\n",
            filename, mode, strerror(errno)
        );
        exit(1);
    }

    return fp;
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 1
    Removing the destination file may be necessary depending on the OS. `rename` removes it and atomically renames the old one in one step, but Windows might not. The C Standard says: ***C11 7.21.4.2** ...If a file named by the string pointed to by new exists prior to the call to the rename function, the behavior is implementation-defined.* – chqrlie Jan 29 '17 at 07:45
  • 1
    You should pass the array sizes to `sscanf` to prevent potential buffer overflows. And you should not use these arrays if `sscanf` failed. Just printing the source line seems the correct option. My version would strip the rest of the file, which is probably inadequate. – chqrlie Jan 29 '17 at 07:46
  • 1
    The name `old_file_name` is misleading, `rename("duplicate.txt", old_file_name)` has the arguments in the correct order `rename(old, new)`. – chqrlie Jan 29 '17 at 07:50