/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file implements the interface in chansrv_fuse_fs.h
 */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif

#include "os_calls.h"
#include "log.h"

#include "chansrv_xfs.h"

/*
 * Skip this module if FUSE is not supported. A standards-compliant C
 * translation unit must contain at least one declaration (C99:6.9), and we've
 * fulfilled that requirement by this stage.
 */
#ifdef XRDP_FUSE

#define INODE_TABLE_ALLOCATION_INITIAL     4096
#define INODE_TABLE_ALLOCATION_GRANULARITY 100

/* inum of the delete pending directory */
#define DELETE_PENDING_ID 2

/*
 * A double-linked list of inodes, sorted by inum
 *
 * The elements in the list are sorted in increasing inum order, as this
 * allows a directory enumeration to be easily resumed if elements
 * are removed or added. See xfs_readdir() for details on this.
 */
typedef struct xfs_inode_all XFS_INODE_ALL;
typedef struct xfs_list
{
    XFS_INODE_ALL *begin;
    XFS_INODE_ALL *end;
} XFS_LIST;

/*
 * A complete inode, including the private elements used by the
 * implementation
 */
typedef struct xfs_inode_all
{
    XFS_INODE      pub;                /* Public elements                  */
    /*
     * Directory linkage elements
     *
     * Because we don't support hard-linking, elements can be stored in
     * one and only one directory:-
     */
    struct xfs_inode_all *parent;      /* Parent inode                     */
    struct xfs_inode_all *next;        /* Next entry in parent             */
    struct xfs_inode_all *previous;    /* Previous entry in parent         */
    XFS_LIST             dir;          /* Directory only - children        */
    /*
     * Other private elements
     */
    unsigned int         open_count;   /* Regular files only               */
} XFS_INODE_ALL;


/* the xrdp file system in memory
 *
 * inode_table    allows for O(1) access to any file based on the inum.
 *                Index 0 is unused, so we can use an inode of zero for
 *                an invalid inode, and avoid off-by-one errors index
 *                1 is our '.' directory.
 *                2 is the delete pending directory, where we can place
 *                  inodes with a positive open count which are
 *                  deleted.
 * free_list      List of free inode numbers. Allows for O(1) access to
 *                a free node, provided the free list is not empty.
 */
struct xfs_fs
{
    XFS_INODE_ALL    **inode_table;  /* a table of entries; can grow.        */
    fuse_ino_t        *free_list;    /* Free inodes                          */
    unsigned int inode_count;        /* Current number of inodes             */
    unsigned int free_count;         /* Size of free_list                    */
    unsigned int generation;         /* Changes when an inode is deleted     */
};

/* A directory handle
 *
 * inum           inum of the directory being scanned
 * generation     Generation of the inum we opened
 */
struct xfs_dir_handle
{
    fuse_ino_t  inum;
    tui32       generation;
};


/*  ------------------------------------------------------------------------ */
static int
grow_xfs(struct xfs_fs *xfs, unsigned int extra_inodes)
{
    int result = 0;
    unsigned int new_count = xfs->inode_count + extra_inodes;
    XFS_INODE_ALL **new_table;
    fuse_ino_t *new_free_list;

    new_table = (XFS_INODE_ALL **)
                realloc(xfs->inode_table, new_count * sizeof(new_table[0]));
    if (new_table != NULL)
    {
        unsigned int i;
        for (i = xfs->inode_count ; i < new_count ; ++i)
        {
            new_table[i] = NULL;
        }
        xfs->inode_table = new_table;

        new_free_list = (fuse_ino_t *)
                        realloc(xfs->free_list,
                                new_count * sizeof(new_free_list[0]));
        if (new_free_list)
        {
            /* Add the new inodes in to the new_free_list, so the lowest
             * number is allocated first
             */
            i = new_count;
            while (i > xfs->inode_count)
            {
                new_free_list[xfs->free_count++] = --i;
            }

            xfs->free_list = new_free_list;
            xfs->inode_count = new_count;

            result = 1;
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
static void
add_inode_to_list(XFS_LIST *list, XFS_INODE_ALL *xino)
{
    fuse_ino_t inum = xino->pub.inum;

    /* Find the element we need to insert after */
    XFS_INODE_ALL *predecessor = list->end;
    while (predecessor != NULL && predecessor->pub.inum > inum)
    {
        predecessor = predecessor->previous;
    }

    if (predecessor == NULL)
    {
        /* Inserting at the beginning */
        /* Set up links in node */
        xino->next = list->begin;
        xino->previous = NULL;

        /* Set up back-link to node */
        if (list->begin == NULL)
        {
            /* We are the last node */
            list->end = xino;
        }
        else
        {
            list->begin->previous = xino;
        }
        /* Set up forward-link to node */
        list->begin = xino;
    }
    else
    {
        /* Set up links in node */
        xino->next = predecessor->next;
        xino->previous = predecessor;

        /* Set up back-link to node */
        if (predecessor->next == NULL)
        {
            list->end = xino;
        }
        else
        {
            predecessor->next->previous = xino;
        }
        /* Set up forward-link to node */
        predecessor->next = xino;
    }
}

/*  ------------------------------------------------------------------------ */
static void
remove_inode_from_list(XFS_LIST *list, XFS_INODE_ALL *xino)
{
    if (xino->previous == NULL)
    {
        /* First element */
        list->begin = xino->next;
    }
    else
    {
        xino->previous->next = xino->next;
    }

    if (xino->next == NULL)
    {
        /* Last element */
        list->end = xino->previous;
    }
    else
    {
        xino->next->previous = xino->previous;
    }

}

/*  ------------------------------------------------------------------------ */
static void
link_inode_into_directory_node(XFS_INODE_ALL *dinode, XFS_INODE_ALL *xino)
{
    xino->parent = dinode;
    add_inode_to_list(&dinode->dir, xino);
}

/*  ------------------------------------------------------------------------ */
static void
unlink_inode_from_parent(XFS_INODE_ALL *xino)
{
    remove_inode_from_list(&xino->parent->dir, xino);

    xino->next = NULL;
    xino->previous = NULL;
    xino->parent = NULL;
}

/*  ------------------------------------------------------------------------ */
struct xfs_fs *
xfs_create_xfs_fs(mode_t umask, uid_t uid, gid_t gid)
{
    struct xfs_fs *xfs = g_new0(struct xfs_fs, 1);
    XFS_INODE_ALL *xino1 = NULL;
    XFS_INODE_ALL *xino2 = NULL;

    if (xfs != NULL)
    {
        xfs->inode_count = 0;
        xfs->free_count = 0;
        xfs->inode_table = NULL;
        xfs->free_list   = NULL;
        xfs->generation = 1;

        /* xfs->inode_table check should be superfluous here, but it
         * prevents cppcheck 2.2/2.3 generating a false positive nullPointer
         * report */
        if (!grow_xfs(xfs, INODE_TABLE_ALLOCATION_INITIAL) ||
                xfs->inode_table == NULL ||
                (xino1 = g_new0(XFS_INODE_ALL, 1)) == NULL ||
                (xino2 = g_new0(XFS_INODE_ALL, 1)) == NULL)
        {
            free(xino1);
            free(xino2);
            xfs_delete_xfs_fs(xfs);
            xfs = NULL;
        }
        else
        {
            /*
             * The use of grow_xfs to allocate the inode table will make
             * inodes 0,  1 (FUSE_ROOT_ID) and 2 (DELETE_PENDING_ID) the first
             * available free inodes. We can ignore these */
            xfs->free_count -= 3;

            xfs->inode_table[0] = NULL;
            xfs->inode_table[FUSE_ROOT_ID] = xino1;
            xfs->inode_table[DELETE_PENDING_ID] = xino2;

            xino1->pub.inum = FUSE_ROOT_ID;
            xino1->pub.mode = (S_IFDIR | 0777) & ~umask;
            xino1->pub.uid = uid;
            xino1->pub.gid = gid;
            xino1->pub.size = 0;
            xino1->pub.atime = time(0);
            xino1->pub.mtime = xino1->pub.atime;
            xino1->pub.ctime = xino1->pub.atime;
            strcpy(xino1->pub.name, ".");
            xino1->pub.generation = xfs->generation;
            xino1->pub.is_redirected = 0;
            xino1->pub.device_id = 0;

            /*
             * FUSE_ROOT_ID has no parent rather than being a parent
             * of itself. This is intentional */
            xino1->parent = NULL;
            xino1->next = NULL;
            xino1->previous = NULL;
            xino1->dir.begin = NULL;
            xino1->dir.end = NULL;

            xino2->pub.inum = DELETE_PENDING_ID;
            xino2->pub.mode = (S_IFDIR | 0777) & ~umask;
            xino2->pub.uid = uid;
            xino2->pub.gid = gid;
            xino2->pub.size = 0;
            xino2->pub.atime = time(0);
            xino2->pub.mtime = xino2->pub.atime;
            xino2->pub.ctime = xino2->pub.atime;
            strcpy(xino2->pub.name, ".delete-pending");
            xino2->pub.generation = xfs->generation;
            xino2->pub.is_redirected = 0;
            xino2->pub.device_id = 0;

            xino2->parent = NULL;
            xino2->next = NULL;
            xino2->previous = NULL;
            xino2->dir.begin = NULL;
            xino2->dir.end = NULL;
            /*
             * Uncomment this line to make the .delete-pending
             * directory visible to the user in the root
             */
            /* link_inode_into_directory_node(xino1, xino2); */
        }
    }

    return xfs;
}

/*  ------------------------------------------------------------------------ */
void
xfs_delete_xfs_fs(struct xfs_fs *xfs)
{
    if (xfs == NULL)
    {
        return;
    }

    if (xfs->inode_table != NULL)
    {
        size_t i;
        for (i = 0 ; i < xfs->inode_count; ++i)
        {
            free(xfs->inode_table[i]);
        }
    }
    free(xfs->inode_table);
    free(xfs->free_list);
    free(xfs);
}

/*  ------------------------------------------------------------------------ */
XFS_INODE *
xfs_add_entry(struct xfs_fs *xfs, fuse_ino_t parent_inum,
              const char *name, mode_t mode)
{
    XFS_INODE *result = NULL;
    XFS_INODE_ALL *parent = NULL;

    /* Checks:-
     * 1) the parent exists (and is a directory)
     * 2) the caller is not inserting into the .delete-pending directory,
     * 3) Name's not too long
     * 4) Entry does not already exist
     */
    if (parent_inum < xfs->inode_count &&
            ((parent = xfs->inode_table[parent_inum]) != NULL) &&
            (parent->pub.mode & S_IFDIR) != 0 &&
            parent_inum != DELETE_PENDING_ID &&
            strlen(name) <= XFS_MAXFILENAMELEN &&
            !xfs_lookup_in_dir(xfs, parent_inum, name))
    {
        /* Sanitise the mode so one-and-only-one of S_IFDIR and
         * S_IFREG is set */
        if ((mode & S_IFDIR) != 0)
        {
            mode = (mode & 0777) | S_IFDIR;
        }
        else
        {
            mode = (mode & 0777) | S_IFREG;
        }

        /* Space for a new entry? */
        if (xfs->free_count > 0 ||
                grow_xfs(xfs, INODE_TABLE_ALLOCATION_GRANULARITY))
        {
            XFS_INODE_ALL *xino =  NULL;

            if ((xino = g_new0(XFS_INODE_ALL, 1)) != NULL)
            {
                fuse_ino_t inum = xfs->free_list[--xfs->free_count];
                if (xfs->inode_table[inum] != NULL)
                {
                    LOG_DEVEL(LOG_LEVEL_ERROR, "Unexpected non-NULL value in inode table "
                              "entry %ld", inum);
                }
                xfs->inode_table[inum] = xino;
                xino->pub.inum = inum;
                xino->pub.mode = mode;
                xino->pub.uid = parent->pub.uid;
                xino->pub.gid = parent->pub.gid;
                if (mode & S_IFDIR)
                {
                    xino->pub.size = 4096;
                }
                else
                {
                    xino->pub.size = 0;
                }
                xino->pub.atime = time(0);
                xino->pub.mtime = xino->pub.atime;
                xino->pub.ctime = xino->pub.atime;
                strcpy(xino->pub.name, name);
                xino->pub.generation = xfs->generation;
                xino->pub.is_redirected = parent->pub.is_redirected;
                xino->pub.device_id = parent->pub.device_id;
                xino->pub.lindex = 0;

                xino->parent = NULL;
                xino->next = NULL;
                xino->previous = NULL;
                link_inode_into_directory_node(parent, xino);
                result = &xino->pub;
            }
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
void
xfs_remove_directory_contents(struct xfs_fs *xfs, fuse_ino_t inum)
{
    XFS_INODE_ALL *xino =  NULL;

    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            ((xino->pub.mode & S_IFDIR) != 0))
    {
        XFS_INODE_ALL *e;
        while ((e = xino->dir.end) != NULL)
        {
            xfs_remove_entry(xfs, e->pub.inum);
        }
    }
}

/*  ------------------------------------------------------------------------ */
void
xfs_remove_entry(struct xfs_fs *xfs, fuse_ino_t inum)
{
    XFS_INODE_ALL *xino =  NULL;

    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL))
    {
        if ((xino->pub.mode & S_IFDIR) != 0)
        {
            xfs_remove_directory_contents(xfs, inum);
        }

        unlink_inode_from_parent(xino);
        if ((xino->pub.mode & S_IFREG) != 0 && xino->open_count > 0)
        {
            link_inode_into_directory_node(
                xfs->inode_table[DELETE_PENDING_ID], xino);
        }
        else
        {
            xfs->free_list[xfs->free_count++] = inum;
            xfs->inode_table[inum] = NULL;
            /*
             * Bump the generation when we return an inum to the free list,
             * so that the caller can distinguish re-uses of the same inum.
             */
            ++xfs->generation;
            free(xino);
        }
    }
}

/*  ------------------------------------------------------------------------ */
XFS_INODE *
xfs_get(struct xfs_fs *xfs, fuse_ino_t inum)
{
    return (inum < xfs->inode_count) ? &xfs->inode_table[inum]->pub : NULL;
}

/*  ------------------------------------------------------------------------ */
char *
xfs_get_full_path(struct xfs_fs *xfs, fuse_ino_t inum)
{
    char *result = NULL;
    XFS_INODE_ALL *xino =  NULL;

    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL))
    {
        if (xino->pub.inum == FUSE_ROOT_ID)
        {
            return strdup("/");
        }
        else
        {
            /*
             * Add up the lengths of all the names up to the root,
             * allowing one extra char for a '/' prefix for each element
             */
            size_t len = 0;
            XFS_INODE_ALL *p;
            for (p = xino ; p && p->pub.inum != FUSE_ROOT_ID ; p = p->parent)
            {
                len += strlen(p->pub.name);
                ++len; /* Allow for '/' prefix */
            }

            result = (char *) malloc(len + 1);
            if (result != NULL)
            {
                /* Construct the path from the end */
                char *end = result + len;
                *end = '\0';

                for (p = xino ; p && p->pub.inum != FUSE_ROOT_ID ; p = p->parent)
                {
                    len = strlen(p->pub.name);
                    end -= (len + 1);
                    *end = '/';
                    memcpy(end + 1, p->pub.name, len);
                }
            }
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
XFS_INODE *
xfs_lookup_in_dir(struct xfs_fs *xfs, fuse_ino_t inum, const char *name)
{
    XFS_INODE_ALL *xino;
    XFS_INODE *result = NULL;
    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            (xino->pub.mode & S_IFDIR) != 0)
    {
        XFS_INODE_ALL *p;
        for (p = xino->dir.begin ; p != NULL; p = p->next)
        {
            if (strcmp(p->pub.name, name) == 0)
            {
                result = &p->pub;
                break;
            }
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
int
xfs_is_dir_empty(struct xfs_fs *xfs, fuse_ino_t inum)
{
    XFS_INODE_ALL *xino =  NULL;
    int result = 0;

    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            (xino->pub.mode & S_IFDIR) != 0)
    {
        result = (xino->dir.begin == NULL);
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
unsigned int
xfs_is_under(struct xfs_fs *xfs, fuse_ino_t dir, fuse_ino_t entry)
{
    unsigned int result = 0;

    XFS_INODE_ALL *dxino =  NULL;
    XFS_INODE_ALL *exino =  NULL;

    if (dir < xfs->inode_count &&
            ((dxino = xfs->inode_table[dir]) != NULL) &&
            (dxino->pub.mode & S_IFDIR) != 0 &&
            entry < xfs->inode_count &&
            ((exino = xfs->inode_table[entry]) != NULL))
    {
        unsigned int count = 0;

        while (exino != NULL && exino != dxino)
        {
            ++count;
            exino = exino->parent;
        }

        if (exino != NULL)
        {
            result = count;
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
struct xfs_dir_handle *
xfs_opendir(struct xfs_fs *xfs, fuse_ino_t dir)
{
    XFS_INODE_ALL *xino =  NULL;
    struct xfs_dir_handle *result = NULL;

    if (dir < xfs->inode_count &&
            ((xino = xfs->inode_table[dir]) != NULL) &&
            (xino->pub.mode & S_IFDIR) != 0)
    {
        result = g_new0(struct xfs_dir_handle, 1);
        if (result)
        {
            result->inum = xino->pub.inum;
            result->generation = xino->pub.generation;
        }
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
XFS_INODE *
xfs_readdir(struct xfs_fs *xfs, struct xfs_dir_handle *handle, off_t *off)
{
    XFS_INODE_ALL *result = NULL;
    XFS_INODE_ALL *dxino = NULL;
    XFS_INODE_ALL *xino = NULL;

    /* Check the directory is still valid */
    if (handle->inum < xfs->inode_count &&
            ((dxino = xfs->inode_table[handle->inum]) != NULL) &&
            (dxino->pub.mode & S_IFDIR) != 0 &&
            handle->generation == dxino->pub.generation)
    {
        fuse_ino_t inum;

        if (*off == (off_t) -1)
        {
            /* We're at the end already */
        }
        else if ((inum = *off) == 0)
        {
            /* First call */
            result = dxino->dir.begin;
        }
        else if (inum < xfs->inode_count &&
                 (xino = xfs->inode_table[inum]) != 0 &&
                 xino->parent == dxino)
        {
            /* The node we're pointing to is still valid */
            result = xino;
        }
        else
        {
            /*
             * The file we wanted has been pulled out from under us.
             * We will look forward in the inode table to try to
             * discover the next inode in the directory. Because
             * files are stored in inode order, this guarantees
             * we'll meet POSIX requirements.
             */
            for (inum = inum + 1 ; inum < xfs->inode_count ; ++inum)
            {
                if ((xino = xfs->inode_table[inum]) != 0 &&
                        xino->parent == dxino)
                {
                    result = xino;
                    break;
                }
            }
        }
    }

    /* Update the offset */
    if (result == NULL || result->next == NULL)
    {
        /* We're done */
        *off = (off_t) -1;
    }
    else
    {
        *off = (off_t)result->next->pub.inum;
    }

    /* Caller only sees public interface to the result */
    return (result) ? &result->pub : NULL;
}

/*  ------------------------------------------------------------------------ */
void
xfs_closedir(struct xfs_fs *xfs, struct xfs_dir_handle *handle)
{
    free(handle);
}

/*  ------------------------------------------------------------------------ */
void
xfs_increment_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum)
{
    XFS_INODE_ALL *xino;
    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            (xino->pub.mode & S_IFREG) != 0)
    {
        ++xino->open_count;
    }
}

/*  ------------------------------------------------------------------------ */
void
xfs_decrement_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum)
{
    XFS_INODE_ALL *xino;
    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            (xino->pub.mode & S_IFREG) != 0)
    {
        if (xino->open_count > 0)
        {
            --xino->open_count;
        }

        if (xino->open_count == 0 &&
                xino->parent == xfs->inode_table[DELETE_PENDING_ID])
        {
            /* We can get rid of this one now */
            xfs_remove_entry(xfs, inum);
        }
    }
}

/*  ------------------------------------------------------------------------ */
unsigned int
xfs_get_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum)
{
    unsigned int result = 0;
    XFS_INODE_ALL *xino;
    if (inum < xfs->inode_count &&
            ((xino = xfs->inode_table[inum]) != NULL) &&
            (xino->pub.mode & S_IFREG) != 0)
    {
        result = xino->open_count;
    }

    return result;
}

/*  ------------------------------------------------------------------------ */
void
xfs_delete_redirected_entries_with_device_id(struct xfs_fs *xfs,
        tui32 device_id)
{
    fuse_ino_t inum;
    XFS_INODE_ALL *xino;

    /* Using xfs_remove_entry() is convenient, but it recurses
     * in to directories. To make sure all entries are removed, set the
     * open_count of all affected files to 0 first
     */
    for (inum = FUSE_ROOT_ID; inum < xfs->inode_count; ++inum)
    {
        if ((xino = xfs->inode_table[inum]) != NULL &&
                xino->pub.is_redirected != 0 &&
                xino->pub.device_id == device_id &&
                (xino->pub.mode & S_IFREG) != 0)
        {
            xino->open_count = 0;
        }
    }

    /* Now we can be sure everything will be deleted correctly */
    for (inum = FUSE_ROOT_ID; inum < xfs->inode_count; ++inum)
    {
        if ((xino = xfs->inode_table[inum]) != NULL &&
                xino->pub.is_redirected != 0 &&
                xino->pub.device_id == device_id)
        {
            xfs_remove_entry(xfs, xino->pub.inum);
        }
    }
}

/*  ------------------------------------------------------------------------ */
int
xfs_check_move_entry(struct xfs_fs *xfs, fuse_ino_t inum,
                     fuse_ino_t new_parent_inum, const char *name)
{
    XFS_INODE_ALL *xino;
    XFS_INODE_ALL *parent;

    return
        (strlen(name) <= XFS_MAXFILENAMELEN &&
         inum < xfs->inode_count &&
         ((xino = xfs->inode_table[inum]) != NULL) &&
         new_parent_inum != DELETE_PENDING_ID &&
         new_parent_inum < xfs->inode_count &&
         ((parent = xfs->inode_table[new_parent_inum]) != NULL) &&
         (parent->pub.mode & S_IFDIR) != 0 &&
         xfs_is_under(xfs, inum, new_parent_inum) == 0);
}

/*  ------------------------------------------------------------------------ */
int
xfs_move_entry(struct xfs_fs *xfs, fuse_ino_t inum,
               fuse_ino_t new_parent_inum, const char *name)
{
    int result = EINVAL;
    XFS_INODE_ALL *xino;
    XFS_INODE_ALL *parent;
    XFS_INODE *dest;

    if (xfs_check_move_entry(xfs, inum, new_parent_inum, name))
    {
        xino = xfs->inode_table[inum];
        parent = xfs->inode_table[new_parent_inum];

        if (xino->parent != parent)
        {
            /* We're moving between directories */

            /* Does the target name already exist in the destination? */
            if ((dest = xfs_lookup_in_dir(xfs, new_parent_inum, name)) != NULL)
            {
                xfs_remove_entry(xfs, dest->inum);
            }

            unlink_inode_from_parent(xino);
            link_inode_into_directory_node(parent, xino);
            strcpy(xino->pub.name, name);
        }
        else if (strcmp(xino->pub.name, name) != 0)
        {
            /* Same directory, but name has changed */
            if ((dest = xfs_lookup_in_dir(xfs, new_parent_inum, name)) != NULL)
            {
                xfs_remove_entry(xfs, dest->inum);
            }
            strcpy(xino->pub.name, name);
        }
        result = 0;
    }

    return result;
}
#endif  /* XRDP_FUSE */
