Search code examples
cfileserverclientstrtok

C appending number to filename if file already exists


Im trying to write a function that will append a number to a filename if that file already exists before I create the new file. This is for a file transfer between a client and server.

For example if the client sends a file name "Example.txt" but the server already has a file named "Example.txt" I would like to change the file name to "Example-1.txt" and if that already exists increase the number to "Example-2.txt" and keep going until the filename doesn't exist.

It should also work in the case the filename is "Ex.x.x.x.x.txt" or "Ex.--1.txt" and change them to "Ex.x.x.x.x-1.txt" and "Ex.--2.txt" respectively.

I have tried using the strtok() method but the code has gotten long and messy. I have tried searching online and I haven't found anything regarding this problem. I feel like I am trying to reinvent the wheel when there has to be a better way to solve this problem.

EDIT- This is what I came up with. One problem that I can see with it is at a certain point when the number to append is too big, there will be a segfault because Im hardcoding the extra memory to allocate to file path.

void check_if_file_exists(char **filepath, const char *filename, const char *directory)
{
    struct stat file_status;

    if (stat(*filepath, &file_status) == 0)
    {
        char *final_filepath, *new_filepath, *filename_extension;
        size_t filepath_size;
        int append;

        filepath_size = strlen(filename) + strlen(directory) + 64;
        append = 1;

        final_filepath = safe_malloc(filepath_size);
        new_filepath = safe_malloc(filepath_size);

        strcpy(new_filepath, directory);

        filename_extension = strrchr(filename, '.');

        strncat(new_filepath, filename, filename_extension - filename);
        strcat(new_filepath, "-%d");
        if (filename_extension != NULL)
        {
            strcat(new_filepath, filename_extension);
        }

        do
        {
            sprintf(final_filepath, new_filepath, append++);

        } while (stat(final_filepath, &file_status) == 0);

        *filepath = final_filepath;

        free(new_filepath);
    }
}

void *safe_malloc(size_t size)
{
    void *ptr = malloc(size);

    if (!ptr && size > 0)
    {
        perror("Malloc failed\n");
        exit(EXIT_FAILURE);
    }

    return ptr;
}

Solution

  • Readers... FYI: Before DV'ing, please examine the timestamps and edit revisions of the OP's question and this answer. The code now present in the question appeared hours after this answer was posted. (Or, go ahead and DV, if you must.)

    So, here's a prototype of what might work.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <errno.h>
    
    int nextName( char *cp ) {
        char *sufx = strrchr( cp, '.' );
        char *sp, hold[16] = {0}; // enough for ".txt" or ".html"
    
        if( sufx )
            strcpy( hold, sp = sufx );
        else
            sp = cp + strlen( cp );
    
        int i = 0;
    
        do {
            struct stat sbuf;
            if( stat( cp, &sbuf ) != 0 && errno == ENOENT )
                return 1;
    
    printf( "%s exists... Moving on\n", cp ); // debugging
    
            sprintf( sp, "-%02d%s ", i, hold );
        } while( ++i < 100 );
    
        return 0;
    }
    
    int main( void ) {
        char str[ 32 ] = "Example.txt";
    
        puts( str );
        if( !nextName( str ) ) {
            /* Handle case where no higher version number is possible */
        }
        puts( str );
    
        return 0;
    }
    

    Create two files, "Example.txt" and "Example-00.txt", in the current directory. This will detect their existence and modify the filename until it finds "Example-01.txt" is not currently there. And so on...

    This should even work if the filename has no extension.
    Example: "foobar" and "foobar-01"...

    Caution: The buffer containing the desired path must be LARGER so that the extra 3 characters can be 'tacked in/on' to the filename.

    There is an inherent race condition if multiple processes may be writing to the same directory.

    (You could trivially change this to use 000->999 versions of each file. Adapt to suit your needs.)


    EDIT:
    Having considered that a path could be "this.dir/filename", the above needs to be improved by the following:

    /* Scan right to left examining only the filename part of a path. */
    char *fndExt( char *cp ) {
        char *p = cp + strlen(cp) - 1;
        for( ; p >= cp && *p != '/' && *p != '\\'; p-- )
            if( *p == '.' )
                return p;
        return NULL;
    }
    
    int nextName( char *cp ) {
        char *sp = fndExt( cp, '.' );
        char hold[16] = {0}; // enough for ".txt" or ".html"
    
        if( sp )
            strcpy( hold, sp );
        else
            sp = cp + strlen( cp );
    
        int i = 0;
    
        do {
            /* same as above */
    

    There may be a need for a larger hold[] buffer to account for pathologically long filenames that have a '.' more than 30 characters from the end of the name. Or, findExt() could be modified to only look for '.' 4-5 characters from the end of the filename and presume that to be the file's extension. Much depends on context. Is the file named "Mr.Bar" a name with an extension?