1

I wrote a function in C that implements the mv command using Linux system calls. However, my implementation only works when renaming files. When I try to use my "mv" implementation to move a file to a different directory, the move seems to be unsuccessful, meaning that the file just stays in its current directory.

Here is my function for my "mv" implementation:

void moveFile(char *sourcePath, char *destinationPath) {
    char *error;
    /* determine if Source Path exists */
    if(access(sourcePath, F_OK) != 0) {
        error = "mv: cannot stat '";
        write(2, error, strlen(error));
        write(2, sourcePath, strlen(sourcePath));
        write(2, "': No such file or directory\n", strlen("': No such file or directory\n"));
    }
    
    /* determine if Source Path and Destination Path are identical */
    if(strcmp(sourcePath, destinationPath) == 0) {
        write(2, "mv: ", strlen("mv: "));
        write(2, sourcePath, strlen(sourcePath));
        write(2, " and ", strlen(" and "));
        write(2, destinationPath, strlen(destinationPath));
        write(2, " are the same\n", strlen(" are the same\n"));
    }

    else {
        /* rename or move the file from sourcePath to destinationPath */
        int ret_val = rename(sourcePath, destinationPath);
        if(ret_val != 0) {
            write(2, "move unsuccessful\n", strlen("move unsuccessful\n"));
        }
    }
}

How can I fix this, so that it works for moving files to different directories?

  • 2
    Please provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). That is, include the test code and any inputs that are can be used to reproduce the problem. – kaylum Oct 09 '20 at 09:05
  • 3
    You should display `errno` if [`rename`](https://linux.die.net/man/3/rename) returns something != 0. – Jabberwocky Oct 09 '20 at 09:10
  • Looking at the man page for `rename`: *oldpath* can specify a directory. In this case, *newpath* must either not exist, or it must specify an empty directory. – WedaPashi Oct 09 '20 at 09:12
  • 1
    @WedaPashi Yes but that may not be relevant as OP says the source is a file: `When I try to use my "mv" implementation to move a file to a different directory` – kaylum Oct 09 '20 at 09:14
  • What sort of items (files or directories) do `sourcePath` and `destinationPath` refer to? If `sourcePath` is a regular file and `destinationPath` is a directory, then you may be getting stung by the following from the man page: "If the _old_ argument points to the pathname of a file that is not a directory, the _new_ argument shall not point to the pathname of a directory." – Ian Abbott Oct 09 '20 at 09:20
  • 2
    Also, my recollection is that `rename()` does not work if the source and destination are on different filesystems, even if all the other criteria are satisfied. – Kevin Boone Oct 09 '20 at 09:46
  • `access` can fail for many reasons other than nonexistence. Your code will be a lot cleaner if you just write things like `if(access(sourcePath,...) {perror(sourcePath);}` – William Pursell Oct 09 '20 at 14:01
  • a simple way would be: `sprintf( buffer, "mv %s %s", oldname newpath); system( buffer );` – user3629249 Oct 10 '20 at 22:37

1 Answers1

0

from man 2 remane

   If newpath exists but the operation fails for some reason, rename()
   guarantees to leave an instance of newpath in place.
   

Out of the box, rename will be able to

  • move one file to another file
  • move one directory to another directory.

The newpath will always be the old file new name, not the containing directory.

You need another behavior: moving a file to a directory.

You must detect that newpath is a directory

// for basename
#include <libgen.h>
// for sprintf
#include <stdio.h> 
// for stat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// get info on destination path
struct stat buf;
if(stat(destinationPath, &buf) == 0){
    // destinationPath already exists, we must choose what to do
    
    char newpath[MAX_PATH];    
    if(S_ISDIR(buf.st_mode)) 
    {        
        sprintf(newpath, "%s/%s", destinationPath, basename(sourcePath) ;        
    } else {
        sprintf(newpath, "%s", destinationPath ;
    }
    rename(sourcePath, newpath);
} else {
    // in that case, `stat` failed, indication that destinationPath did not exist earlier...
    
    remane(sourcePath, destinationPath);
}
Mathieu
  • 8,840
  • 7
  • 32
  • 45