/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * xrdp device redirection - only drive redirection is currently supported
 *
 * Copyright (C) Laxmikant Rashinkar 2013 LK.Rashinkar@gmail.com
 *
 * 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.
 */

/*
 * TODO
 *      o there is one difference in the protocol initialization
 *        sequence upon reconnection: if a user is already logged on,
 *        send a SERVER_USER_LOGGED_ON msg as per section 3.3.5.1.5
 *
 *      o how to announce / delete a drive after a connection has been
 *        established?
 *
 *      o need to support multiple drives;
 *      o for multiple drives, we cannot have global device_id's and file_id's
 *        and all functions must be reenterant
 *      o replace printf's with log_xxx
 *      o mark local funcs with static
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include "arch.h"
#include "parse.h"
#include "os_calls.h"
#include "log.h"
#include "chansrv_fuse.h"
#include "devredir.h"
#include "smartcard.h"

/* module based logging */
#define LOG_ERROR   0
#define LOG_INFO    1
#define LOG_DEBUG   2

#ifndef LOG_LEVEL
#define LOG_LEVEL   LOG_ERROR
#endif

#define log_error(_params...)                           \
{                                                       \
    g_write("[%10.10u]: DEV_REDIR  %s: %d : ERROR: ",   \
            g_time3(), __func__, __LINE__);             \
    g_writeln (_params);                                \
}

#define log_info(_params...)                            \
{                                                       \
    if (LOG_INFO <= LOG_LEVEL)                          \
    {                                                   \
        g_write("[%10.10u]: DEV_REDIR  %s: %d : ",      \
                g_time3(), __func__, __LINE__);         \
        g_writeln (_params);                            \
    }                                                   \
}

#define log_debug(_params...)                           \
{                                                       \
    if (LOG_DEBUG <= LOG_LEVEL)                         \
    {                                                   \
        g_write("[%10.10u]: DEV_REDIR  %s: %d : ",      \
                g_time3(), __func__, __LINE__);         \
        g_writeln (_params);                            \
    }                                                   \
}

/* globals */
extern int g_rdpdr_chan_id; /* in chansrv.c */
int g_is_printer_redir_supported = 0;
int g_is_port_redir_supported = 0;
int g_is_drive_redir_supported = 0;
int g_is_smartcard_redir_supported = 0;
int g_drive_redir_version = 1;
char g_full_name_for_filesystem[1024];
tui32 g_completion_id = 1;

tui32 g_clientID;           /* unique client ID - announced by client */
tui32 g_device_id;          /* unique device ID - announced by client */
tui16 g_client_rdp_version; /* returned by client                     */
struct stream *g_input_stream = NULL;

void xfuse_devredir_cb_write_file(void *vp, char *buf, size_t length);

/*****************************************************************************/
int APP_CC
dev_redir_init(void)
{
    struct  stream *s;
    int     bytes;
    int     fd;

    union _u
    {
        tui32 clientID;
        char buf[4];
    } u;

    /* get a random number that will act as a unique clientID */
    if ((fd = open("/dev/urandom", O_RDONLY)) != -1)
    {
        if (read(fd, u.buf, 4) != 4)
        {
        }
        close(fd);
    }
    else
    {
        /* /dev/urandom did not work - use address of struct s */
        tui64 u64 = (tui64) (tintptr) &s;
        u.clientID = (tui32) u64;
    }

    /* setup stream */
    xstream_new(s, 1024);

    /* initiate drive redirection protocol by sending Server Announce Req  */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_SERVER_ANNOUNCE);
    xstream_wr_u16_le(s, 0x0001);  /* server major ver                      */
    xstream_wr_u16_le(s, 0x000C);  /* server minor ver  - pretend 2 b Win 7 */
    xstream_wr_u32_le(s, u.clientID); /* unique ClientID                    */

    /* send data to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
    return 0;
}

/*****************************************************************************/
int APP_CC
dev_redir_deinit(void)
{
    scard_deinit();
    return 0;
}

/**
 * @brief process incoming data
 *
 * @return 0 on success, -1 on failure
 *****************************************************************************/

int APP_CC
dev_redir_data_in(struct stream *s, int chan_id, int chan_flags, int length,
                  int total_length)
{
    struct stream *ls;
    tui16          comp_type;
    tui16          pktID;
    tui16          minor_ver;
    int            rv = 0;

    /*
     * handle packet fragmentation
     */
    if ((chan_flags & 3) == 3)
    {
        /* all data contained in one packet */
        ls = s;
    }
    else
    {
        /* is this is the first packet? */
        if (chan_flags & 1)
            xstream_new(g_input_stream, total_length);

        xstream_copyin(g_input_stream, s->p, length);

        /* in last packet, chan_flags & 0x02 will be true */
        if ((chan_flags & 2) == 0)
            return 0;

        g_input_stream->p = g_input_stream->data;
        ls = g_input_stream;
    }

    /* read header from incoming data */
    xstream_rd_u16_le(ls, comp_type);
    xstream_rd_u16_le(ls, pktID);

    /* for now we only handle core type, not printers */
    if (comp_type != RDPDR_CTYP_CORE)
    {
        log_error("invalid component type in response; expected 0x%x got 0x%x",
                  RDPDR_CTYP_CORE, comp_type);

        rv = -1;
        goto done;
    }

    /* figure out what kind of response we have gotten */
    switch (pktID)
    {
        case PAKID_CORE_CLIENTID_CONFIRM:
            xstream_seek(ls, 2);  /* major version, we ignore it */
            xstream_rd_u16_le(ls, minor_ver);
            xstream_rd_u32_le(ls, g_clientID);

            g_client_rdp_version = minor_ver;

            switch (minor_ver)
            {
                case RDP_CLIENT_50:
                    break;

                case RDP_CLIENT_51:
                    break;

                case RDP_CLIENT_52:
                    break;

                case RDP_CLIENT_60_61:
                    break;
            }
            // LK_TODO dev_redir_send_server_clientID_confirm();
            break;

        case PAKID_CORE_CLIENT_NAME:
            /* client is telling us its computer name; do we even care? */

            /* let client know login was successful */
            dev_redir_send_server_user_logged_on();
            usleep(1000 * 100);

            /* let client know our capabilities */
            dev_redir_send_server_core_cap_req();

            /* send confirm clientID */
            dev_redir_send_server_clientID_confirm();
            break;

        case PAKID_CORE_CLIENT_CAPABILITY:
            dev_redir_proc_client_core_cap_resp(ls);
            break;

        case PAKID_CORE_DEVICELIST_ANNOUNCE:
            devredir_proc_client_devlist_announce_req(ls);
            break;

        case PAKID_CORE_DEVICE_IOCOMPLETION:
            dev_redir_proc_device_iocompletion(ls);
            break;

        default:
            log_error("got unknown response 0x%x", pktID);
            break;
    }

done:

    if (g_input_stream)
    {
        xstream_free(g_input_stream);
        g_input_stream = NULL;
    }

    return rv;
}

/*****************************************************************************/
int APP_CC
dev_redir_get_wait_objs(tbus *objs, int *count, int *timeout)
{
    if (g_is_smartcard_redir_supported)
    {
        return scard_get_wait_objs(objs, count, timeout);
    }
    return 0;
}

/*****************************************************************************/
int APP_CC
dev_redir_check_wait_objs(void)
{
    if (g_is_smartcard_redir_supported)
    {
        return scard_check_wait_objs();
    }
    return 0;
}

/**
 * @brief let client know our capabilities
 *****************************************************************************/

void dev_redir_send_server_core_cap_req(void)
{
    struct stream *s;
    int            bytes;

    xstream_new(s, 1024);

    /* setup header */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_SERVER_CAPABILITY);

    xstream_wr_u16_le(s, 5);                /* num of caps we are sending     */
    xstream_wr_u16_le(s, 0x0000);           /* padding                        */

    /* setup general capability */
    xstream_wr_u16_le(s, CAP_GENERAL_TYPE); /* CapabilityType                 */
    xstream_wr_u16_le(s, 44);               /* CapabilityLength - len of this */
                                            /* CAPABILITY_SET in bytes, inc   */
                                            /* the header                     */
    xstream_wr_u32_le(s, 2);                /* Version                        */
    xstream_wr_u32_le(s, 2);                /* O.S type                       */
    xstream_wr_u32_le(s, 0);                /* O.S version                    */
    xstream_wr_u16_le(s, 1);                /* protocol major version         */
    xstream_wr_u16_le(s, g_client_rdp_version); /* protocol minor version     */
    xstream_wr_u32_le(s, 0xffff);           /* I/O code 1                     */
    xstream_wr_u32_le(s, 0);                /* I/O code 2                     */
    xstream_wr_u32_le(s, 7);                /* Extended PDU                   */
    xstream_wr_u32_le(s, 0);                /* extra flags 1                  */
    xstream_wr_u32_le(s, 0);                /* extra flags 2                  */
    xstream_wr_u32_le(s, 2);                /* special type device cap        */

    /* setup printer capability */
    xstream_wr_u16_le(s, CAP_PRINTER_TYPE);
    xstream_wr_u16_le(s, 8);
    xstream_wr_u32_le(s, 1);

    /* setup serial port capability */
    xstream_wr_u16_le(s, CAP_PORT_TYPE);
    xstream_wr_u16_le(s, 8);
    xstream_wr_u32_le(s, 1);

    /* setup file system capability */
    xstream_wr_u16_le(s, CAP_DRIVE_TYPE);   /* CapabilityType                 */
    xstream_wr_u16_le(s, 8);                /* CapabilityLength - len of this */
                                            /* CAPABILITY_SET in bytes, inc   */
                                            /* the header                     */
    xstream_wr_u32_le(s, 2);                /* Version                        */

    /* setup smart card capability */
    xstream_wr_u16_le(s, CAP_SMARTCARD_TYPE);
    xstream_wr_u16_le(s, 8);
    xstream_wr_u32_le(s, 1);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
}

void dev_redir_send_server_clientID_confirm(void)
{
    struct stream *s;
    int            bytes;

    xstream_new(s, 1024);

    /* setup stream */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_CLIENTID_CONFIRM);
    xstream_wr_u16_le(s, 0x0001);
    xstream_wr_u16_le(s, g_client_rdp_version);
    xstream_wr_u32_le(s, g_clientID);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
}

void dev_redir_send_server_user_logged_on(void)
{
    struct stream *s;
    int            bytes;

    xstream_new(s, 1024);

    /* setup stream */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_USER_LOGGEDON);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
}

void devredir_send_server_device_announce_resp(tui32 device_id)
{
    struct stream *s;
    int            bytes;

    xstream_new(s, 1024);

    /* setup stream */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_DEVICE_REPLY);
    xstream_wr_u32_le(s, device_id);
    xstream_wr_u32_le(s, 0); /* ResultCode */

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
}

/**
 * @return 0 on success, -1 on failure
 *****************************************************************************/

int dev_redir_send_drive_create_request(tui32 device_id,
                                        const char *path,
                                        tui32 DesiredAccess,
                                        tui32 CreateOptions,
                                        tui32 CreateDisposition,
                                        tui32 completion_id)
{
    struct stream *s;
    int            bytes;
    int            len;

    log_debug("DesiredAccess=0x%x CreateDisposition=0x%x CreateOptions=0x%x",
              DesiredAccess, CreateDisposition, CreateOptions);

    /* path in unicode needs this much space */
    len = ((g_mbstowcs(NULL, path, 0) * sizeof(twchar)) / 2) + 2;

    xstream_new(s, 1024 + len);

    devredir_insert_DeviceIoRequest(s,
                                    device_id,
                                    0,
                                    completion_id,
                                    IRP_MJ_CREATE,
                                    0);

    xstream_wr_u32_le(s, DesiredAccess);     /* DesiredAccess              */
    xstream_wr_u32_le(s, 0);                 /* AllocationSize high unused */
    xstream_wr_u32_le(s, 0);                 /* AllocationSize low  unused */
    xstream_wr_u32_le(s, 0);                 /* FileAttributes             */
    xstream_wr_u32_le(s, 3);                 /* SharedAccess LK_TODO       */
    xstream_wr_u32_le(s, CreateDisposition); /* CreateDisposition          */
    xstream_wr_u32_le(s, CreateOptions);     /* CreateOptions              */
    xstream_wr_u32_le(s, len);               /* PathLength                 */
    devredir_cvt_to_unicode(s->p, path);     /* path in unicode            */
    xstream_seek(s, len);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
    return 0;
}

/**
 * Close a request previously created by dev_redir_send_drive_create_request()
 *****************************************************************************/

int dev_redir_send_drive_close_request(tui16 Component, tui16 PacketId,
                                       tui32 DeviceId,
                                       tui32 FileId,
                                       tui32 CompletionId,
                                       tui32 MajorFunction,
                                       tui32 MinorFunc,
                                       int pad_len)
{
    struct stream *s;
    int            bytes;

    xstream_new(s, 1024);

    devredir_insert_DeviceIoRequest(s, DeviceId, FileId, CompletionId,
                                    MajorFunction, MinorFunc);

    if (pad_len)
        xstream_seek(s, pad_len);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
    log_debug("sent close request; expect CID_FILE_CLOSE");
    return 0;
}

/**
 * @brief ask client to enumerate directory
 *
 * Server Drive Query Directory Request
 * DR_DRIVE_QUERY_DIRECTORY_REQ
 *
 *****************************************************************************/
// LK_TODO Path needs to be Unicode
void dev_redir_send_drive_dir_request(IRP *irp, tui32 device_id,
                                      tui32 InitialQuery, char *Path)
{
    struct stream *s;
    int            bytes;
    char           upath[4096]; // LK_TODO need to malloc this
    int            path_len = 0;

    /* during Initial Query, Path cannot be NULL */
    if (InitialQuery)
    {
        if (Path == NULL)
        {
            return;
        }

        /* Path in unicode needs this much space */
        path_len = ((g_mbstowcs(NULL, Path, 0) * sizeof(twchar)) / 2) + 2;
        devredir_cvt_to_unicode(upath, Path);
    }

    xstream_new(s, 1024 + path_len);

    irp->completion_type = CID_DIRECTORY_CONTROL;
    devredir_insert_DeviceIoRequest(s,
                                    device_id,
                                    irp->FileId,
                                    irp->CompletionId,
                                    IRP_MJ_DIRECTORY_CONTROL,
                                    IRP_MN_QUERY_DIRECTORY);

#ifdef USE_SHORT_NAMES_IN_DIR_LISTING
    xstream_wr_u32_le(s, FileBothDirectoryInformation); /* FsInformationClass */
#else
    xstream_wr_u32_le(s, FileDirectoryInformation);  /* FsInformationClass */
#endif
    xstream_wr_u8(s, InitialQuery);                  /* InitialQuery       */

    if (!InitialQuery)
    {
        xstream_wr_u32_le(s, 0);                     /* PathLength         */
        xstream_seek(s, 23);
    }
    else
    {
        xstream_wr_u32_le(s, path_len);              /* PathLength         */
        xstream_seek(s, 23);                         /* Padding            */
        xstream_wr_string(s, upath, path_len);
    }

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);

    xstream_free(s);
}

/******************************************************************************
**                   process data received from client                       **
******************************************************************************/

/**
 * @brief process client's response to our core_capability_req() msg
 *
 * @param   s   stream containing client's response
 *****************************************************************************/
void dev_redir_proc_client_core_cap_resp(struct stream *s)
{
    int i;
    tui16 num_caps;
    tui16 cap_type;
    tui16 cap_len;
    tui32 cap_version;
    char* holdp;

    xstream_rd_u16_le(s, num_caps);
    xstream_seek(s, 2);  /* padding */

    for (i = 0; i < num_caps; i++)
    {
        holdp = s->p;
        xstream_rd_u16_le(s, cap_type);
        xstream_rd_u16_le(s, cap_len);
        xstream_rd_u32_le(s, cap_version);

        switch (cap_type)
        {
            case CAP_GENERAL_TYPE:
                log_debug("got CAP_GENERAL_TYPE");
                break;

            case CAP_PRINTER_TYPE:
                log_debug("got CAP_PRINTER_TYPE");
                g_is_printer_redir_supported = 1;
                break;

            case CAP_PORT_TYPE:
                log_debug("got CAP_PORT_TYPE");
                g_is_port_redir_supported = 1;
                break;

            case CAP_DRIVE_TYPE:
                log_debug("got CAP_DRIVE_TYPE");
                g_is_drive_redir_supported = 1;
                if (cap_version == 2)
                {
                    g_drive_redir_version = 2;
                }
                break;

            case CAP_SMARTCARD_TYPE:
                log_debug("got CAP_SMARTCARD_TYPE");
                g_is_smartcard_redir_supported = 1;
                scard_init();
                break;
        }
        s->p = holdp + cap_len;
    }
}

void devredir_proc_client_devlist_announce_req(struct stream *s)
{
    unsigned int i;
    int   j;
    tui32 device_count;
    tui32 device_type;
    tui32 device_data_len;
    char  preferred_dos_name[9];

    /* get number of devices being announced */
    xstream_rd_u32_le(s, device_count);

    log_debug("num of devices announced: %d", device_count);

    for (i = 0; i < device_count; i++)
    {
        xstream_rd_u32_le(s, device_type);
        xstream_rd_u32_le(s, g_device_id);

        switch (device_type)
        {
            case RDPDR_DTYP_FILESYSTEM:
                /* get preferred DOS name */
                for (j = 0; j < 8; j++)
                {
                    preferred_dos_name[j] = *s->p++;
                }

                /* DOS names that are 8 chars long are not NULL terminated */
                preferred_dos_name[8] = 0;

                /* get device data len */
                xstream_rd_u32_le(s, device_data_len);
                if (device_data_len)
                {
                    xstream_rd_string(g_full_name_for_filesystem, s,
                                     device_data_len);
                }

                log_debug("device_type=FILE_SYSTEM device_id=0x%x dosname=%s "
                          "device_data_len=%d full_name=%s", g_device_id,
                          preferred_dos_name,
                          device_data_len, g_full_name_for_filesystem);

                devredir_send_server_device_announce_resp(g_device_id);

                /* create share directory in xrdp file system;    */
                /* think of this as the mount point for this share */
                xfuse_create_share(g_device_id, preferred_dos_name);
                break;

            case RDPDR_DTYP_SMARTCARD:
                /* get preferred DOS name */
                for (j = 0; j < 8; j++)
                {
                    preferred_dos_name[j] = *s->p++;
                }

                /* DOS names that are 8 chars long are not NULL terminated */
                preferred_dos_name[8] = 0;

                /* for smart cards, device data len always 0 */

                log_debug("device_type=SMARTCARD device_id=0x%x dosname=%s "
                          "device_data_len=%d",
                          g_device_id, preferred_dos_name, device_data_len);

                devredir_send_server_device_announce_resp(g_device_id);
                scard_device_announce(g_device_id);
                break;

            /* we don't yet support these devices */
            case RDPDR_DTYP_SERIAL:
            case RDPDR_DTYP_PARALLEL:
            case RDPDR_DTYP_PRINT:
                log_debug("unsupported dev: 0x%x", device_type);
                break;
        }
    }
}

void APP_CC
dev_redir_proc_device_iocompletion(struct stream *s)
{
    FUSE_DATA *fuse_data = NULL;
    IRP       *irp       = NULL;

    tui32      DeviceId;
    tui32      CompletionId;
    tui32      IoStatus;
    tui32      Length;

    xstream_rd_u32_le(s, DeviceId);
    xstream_rd_u32_le(s, CompletionId);
    xstream_rd_u32_le(s, IoStatus);

    log_debug("entered: IoStatus=0x%x CompletionId=%d", IoStatus, CompletionId);

    if ((irp = devredir_irp_find(CompletionId)) == NULL)
    {
        log_error("IRP with completion ID %d not found", CompletionId);
        return;
    }

    /* if callback has been set, call it */
    if (irp->callback)
    {
        (*irp->callback)(s, irp, DeviceId, CompletionId, IoStatus);
        goto done;
    }

    switch (irp->completion_type)
    {
    case CID_CREATE_DIR_REQ:
        log_debug("got CID_CREATE_DIR_REQ");
        if (IoStatus != NT_STATUS_SUCCESS)
        {
            /* we were trying to create a request to enumerate a dir */
            /* that does not exist; let FUSE know                    */
            fuse_data = devredir_fuse_data_dequeue(irp);
            if (fuse_data)
            {
                xfuse_devredir_cb_enum_dir_done(fuse_data->data_ptr,
                                                IoStatus);
                free(fuse_data);
            }
            devredir_irp_delete(irp);
            return;
        }

        xstream_rd_u32_le(s, irp->FileId);
        log_debug("got CID_CREATE_DIR_REQ IoStatus=0x%x FileId=%d",
                  IoStatus, irp->FileId);

        dev_redir_send_drive_dir_request(irp, DeviceId, 1, irp->pathname);
        break;

    case CID_CREATE_OPEN_REQ:
        xstream_rd_u32_le(s, irp->FileId);

        log_debug("got CID_CREATE_OPEN_REQ IoStatus=0x%x FileId=%d",
                  IoStatus, irp->FileId);

        fuse_data = devredir_fuse_data_dequeue(irp);
        xfuse_devredir_cb_open_file(fuse_data->data_ptr, IoStatus,
                                    DeviceId, irp->FileId);
        if ((irp->type == S_IFDIR) || (IoStatus != NT_STATUS_SUCCESS))
            devredir_irp_delete(irp);
        break;

    case CID_READ:
        log_debug("got CID_READ");
        xstream_rd_u32_le(s, Length);
        fuse_data = devredir_fuse_data_dequeue(irp);

        if (fuse_data == NULL)
        {
            log_error("fuse_data is NULL");
        }
        else
        {
            xfuse_devredir_cb_read_file(fuse_data->data_ptr, s->p, Length);
            devredir_irp_delete(irp);
        }
        break;

    case CID_WRITE:
        log_debug("got CID_WRITE");
        xstream_rd_u32_le(s, Length);
        fuse_data = devredir_fuse_data_dequeue(irp);

        if (fuse_data == NULL)
        {
            log_error("fuse_data is NULL");
        }
        else
        {
            xfuse_devredir_cb_write_file(fuse_data->data_ptr, s->p, Length);
            devredir_irp_delete(irp);
        }
        break;

    case CID_CLOSE:
        log_debug("got CID_CLOSE");
        log_debug("deleting irp with completion_id=%d comp_type=%d",
                  irp->CompletionId, irp->completion_type);
        devredir_irp_delete(irp);
        break;

    case CID_FILE_CLOSE:
        log_debug("got CID_FILE_CLOSE");
        fuse_data = devredir_fuse_data_dequeue(irp);
        xfuse_devredir_cb_file_close(fuse_data->data_ptr);
        devredir_irp_delete(irp);
        break;

    case CID_DIRECTORY_CONTROL:
        log_debug("got CID_DIRECTORY_CONTROL");

        dev_redir_proc_query_dir_response(irp, s, DeviceId,
                                          CompletionId, IoStatus);
        break;

    case CID_RMDIR_OR_FILE:
        log_debug("got CID_RMDIR_OR_FILE");
        xstream_rd_u32_le(s, irp->FileId);
        devredir_proc_cid_rmdir_or_file(irp, IoStatus);
        return;
        break;

    case CID_RMDIR_OR_FILE_RESP:
        log_debug("got CID_RMDIR_OR_FILE_RESP");
        devredir_proc_cid_rmdir_or_file_resp(irp, IoStatus);
        break;

    case CID_RENAME_FILE:
        log_debug("got CID_RENAME_FILE");
        xstream_rd_u32_le(s, irp->FileId);
        devredir_proc_cid_rename_file(irp, IoStatus);
        return;
        break;

    case CID_RENAME_FILE_RESP:
        log_debug("got CID_RENAME_FILE_RESP");
        devredir_proc_cid_rename_file_resp(irp, IoStatus);
        break;

    default:
        log_error("got unknown CompletionID: DeviceId=0x%x "
                  "CompletionId=0x%x IoStatus=0x%x",
                  DeviceId, CompletionId, IoStatus);
        break;
    }

done:

    if (fuse_data)
    {
        log_debug("free FUSE_DATA=%p", fuse_data);
        free(fuse_data);
    }

    log_debug("exiting");
}

void APP_CC
dev_redir_proc_query_dir_response(IRP *irp,
                                  struct stream *s_in,
                                  tui32 DeviceId,
                                  tui32 CompletionId,
                                  tui32 IoStatus)
{
    FUSE_DATA  *fuse_data = NULL;
    XRDP_INODE *xinode;

    tui32 Length;
    tui64 CreationTime;
    tui64 LastAccessTime;
    tui64 LastWriteTime;
    tui64 EndOfFile;
    tui32 FileAttributes;
    tui32 FileNameLength;
    tui32 status;

    char  filename[256];
    unsigned int i = 0;

    xstream_rd_u32_le(s_in, Length);

    if ((IoStatus == NT_STATUS_UNSUCCESSFUL) ||
        (IoStatus == STATUS_NO_MORE_FILES))
    {
        status = (IoStatus == STATUS_NO_MORE_FILES) ? 0 : IoStatus;
        fuse_data = devredir_fuse_data_dequeue(irp);
        xfuse_devredir_cb_enum_dir_done(fuse_data->data_ptr, status);
        irp->completion_type = CID_CLOSE;
        dev_redir_send_drive_close_request(RDPDR_CTYP_CORE,
                                           PAKID_CORE_DEVICE_IOREQUEST,
                                           DeviceId,
                                           irp->FileId,
                                           irp->CompletionId,
                                           IRP_MJ_CLOSE, 0, 32);
        free(fuse_data);
        return;
    }

    /* TODO check status for errors */

    /* process FILE_DIRECTORY_INFORMATION structures */
    while (i < Length)
    {
        log_debug("processing FILE_DIRECTORY_INFORMATION structs");

        xstream_seek(s_in, 4);  /* NextEntryOffset */
        xstream_seek(s_in, 4);  /* FileIndex */
        xstream_rd_u64_le(s_in, CreationTime);
        xstream_rd_u64_le(s_in, LastAccessTime);
        xstream_rd_u64_le(s_in, LastWriteTime);
        xstream_seek(s_in, 8);  /* ChangeTime */
        xstream_rd_u64_le(s_in, EndOfFile);
        xstream_seek(s_in, 8);  /* AllocationSize */
        xstream_rd_u32_le(s_in, FileAttributes);
        xstream_rd_u32_le(s_in, FileNameLength);

#ifdef USE_SHORT_NAMES_IN_DIR_LISTING
        xstream_seek(s_in, 4); /* EaSize */
        xstream_seek(s_in, 1); /* ShortNameLength */
        xstream_seek(s_in, 1); /* Reserved */
        xstream_seek(s_in, 23);  /* ShortName in Unicode */
#endif
        devredir_cvt_from_unicode_len(filename, s_in->p, FileNameLength);

#ifdef USE_SHORT_NAMES_IN_DIR_LISTING
        i += 70 + 23 + FileNameLength;
#else
        i += 64 + FileNameLength;
#endif
        //log_debug("CreationTime:      0x%llx", CreationTime);
        //log_debug("LastAccessTime:    0x%llx", LastAccessTime);
        //log_debug("LastWriteTime:     0x%llx", LastWriteTime);
        //log_debug("EndOfFile:         %lld", EndOfFile);
        //log_debug("FileAttributes:    0x%x", FileAttributes);
#ifdef USE_SHORT_NAMES_IN_DIR_LISTING
        //log_debug("ShortNameLength:   %d", ShortNameLength);
#endif
        //log_debug("FileNameLength:    %d", FileNameLength);
        log_debug("FileName:          %s", filename);

        xinode = g_new0(struct xrdp_inode, 1);
        if (xinode == NULL)
        {
            log_error("system out of memory");
            fuse_data = devredir_fuse_data_peek(irp);
            xfuse_devredir_cb_enum_dir(fuse_data->data_ptr, NULL);
            return;
        }

        strcpy(xinode->name, filename);
        xinode->size = (size_t) EndOfFile;
        xinode->mode = WINDOWS_TO_LINUX_FILE_PERM(FileAttributes);
        xinode->atime = WINDOWS_TO_LINUX_TIME(LastAccessTime);
        xinode->mtime = WINDOWS_TO_LINUX_TIME(LastWriteTime);
        xinode->ctime = WINDOWS_TO_LINUX_TIME(CreationTime);

        /* add this entry to xrdp file system */
        fuse_data = devredir_fuse_data_peek(irp);
        xfuse_devredir_cb_enum_dir(fuse_data->data_ptr, xinode);
    }

    dev_redir_send_drive_dir_request(irp, DeviceId, 0, NULL);
}

/**
 * FUSE calls this function whenever it wants us to enumerate a dir
 *
 * @param fusep     opaque data struct that we just pass back to FUSE when done
 * @param device_id device_id of the redirected share
 * @param path      the dir path to enumerate
 *
 * @return 0 on success, -1 on failure
 *****************************************************************************/

int APP_CC
dev_redir_get_dir_listing(void *fusep, tui32 device_id, const char *path)
{
    tui32  DesiredAccess;
    tui32  CreateOptions;
    tui32  CreateDisposition;
    int    rval;
    IRP   *irp;

    log_debug("fusep=%p", fusep);

    if ((irp = devredir_irp_new()) == NULL)
        return -1;

    strncpy(irp->pathname, path, 255);

    /* convert / to windows compatible \ */
    devredir_cvt_slash(irp->pathname);

    irp->CompletionId = g_completion_id++;
    irp->completion_type = CID_CREATE_DIR_REQ;
    irp->DeviceId = device_id;

    devredir_fuse_data_enqueue(irp, fusep);

    DesiredAccess = DA_FILE_READ_DATA | DA_SYNCHRONIZE;
    CreateOptions = CO_FILE_DIRECTORY_FILE | CO_FILE_SYNCHRONOUS_IO_NONALERT;
    CreateDisposition = CD_FILE_OPEN;

    rval = dev_redir_send_drive_create_request(device_id, irp->pathname,
                                               DesiredAccess, CreateOptions,
                                               CreateDisposition,
                                               irp->CompletionId);

    log_debug("looking for device_id=%d path=%s", device_id, irp->pathname);

    /* when we get a response to dev_redir_send_drive_create_request(), we   */
    /* call dev_redir_send_drive_dir_request(), which needs the following    */
    /* at the end of the path argument                                       */
    if (dev_redir_string_ends_with(irp->pathname, '\\'))
        strcat(irp->pathname, "*");
    else
        strcat(irp->pathname, "\\*");

    return rval;
}

int APP_CC
dev_redir_file_open(void *fusep, tui32 device_id, const char *path,
                    int mode, int type, const char *gen_buf)
{
    tui32  DesiredAccess;
    tui32  CreateOptions;
    tui32  CreateDisposition;
    int    rval;
    IRP   *irp;

    log_debug("device_id=%d path=%s mode=0x%x", device_id, path, mode);

    if ((irp = devredir_irp_new()) == NULL)
        return -1;

    if (type & OP_RENAME_FILE)
    {
        irp->completion_type = CID_RENAME_FILE;
        strncpy(irp->gen_buf, gen_buf, 1023);
    }
    else
    {
        irp->completion_type = CID_CREATE_OPEN_REQ;
    }

    irp->CompletionId = g_completion_id++;
    irp->DeviceId = device_id;

    strncpy(irp->pathname, path, 255);
    devredir_fuse_data_enqueue(irp, fusep);

    if (mode & O_CREAT)
    {
        log_debug("open file in O_CREAT");
        DesiredAccess = 0x0016019f; /* got this value from windows */

        if (type & S_IFDIR)
        {
            log_debug("creating dir");
            CreateOptions = CO_FILE_DIRECTORY_FILE | CO_FILE_SYNCHRONOUS_IO_NONALERT;
            irp->type = S_IFDIR;
        }
        else
        {
            log_debug("creating file");
            CreateOptions = 0x44; /* got this value from windows */
        }

        //CreateDisposition = CD_FILE_CREATE;
        CreateDisposition  = 0x02; /* got this value from windows */
    }
    else
    {
        log_debug("open file in O_RDWR");
#if 1
        /* without the 0x00000010 rdesktop opens files in */
        /* O_RDONLY instead of O_RDWR mode                */
        if (mode & O_RDWR)
            DesiredAccess = DA_FILE_READ_DATA | DA_FILE_WRITE_DATA | DA_SYNCHRONIZE | 0x00000010;
        else
            DesiredAccess = DA_FILE_READ_DATA | DA_SYNCHRONIZE;

        CreateOptions = CO_FILE_SYNCHRONOUS_IO_NONALERT;
        CreateDisposition = CD_FILE_OPEN; // WAS 1
#else
        /* got this value from windows; the 0x00000010 was added by LK; */
        /* without this rdesktop opens files in O_RDONLY instead of     */
        /* O_RDWR mode                                                  */
        DesiredAccess = 0x00120089 | 0x00000010;
        CreateOptions = 0x20060;
        CreateDisposition = 0x01;
#endif
    }

    rval = dev_redir_send_drive_create_request(device_id, path,
                                               DesiredAccess, CreateOptions,
                                               CreateDisposition,
                                               irp->CompletionId);

    return rval;
}

int devredir_file_close(void *fusep, tui32 device_id, tui32 FileId)
{
    IRP *irp;

    log_debug("entered: fusep=%p device_id=%d FileId=%d",
              fusep, device_id, FileId);

#if 0
    if ((irp = devredir_irp_new()) == NULL)
        return -1;

    irp->CompletionId = g_completion_id++;
#else
    if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL)
    {
        log_error("no IRP found with FileId = %d", FileId);
        return -1;
    }
#endif
    irp->completion_type = CID_FILE_CLOSE;
    irp->DeviceId = device_id;
    devredir_fuse_data_enqueue(irp, fusep);

    return dev_redir_send_drive_close_request(RDPDR_CTYP_CORE,
                                              PAKID_CORE_DEVICE_IOREQUEST,
                                              device_id,
                                              FileId,
                                              irp->CompletionId,
                                              IRP_MJ_CLOSE,
                                              0, 32);
}

/**
 * Remove (delete) a directory or file
 *****************************************************************************/

int APP_CC
devredir_rmdir_or_file(void *fusep, tui32 device_id, const char *path, int mode)
{
    tui32  DesiredAccess;
    tui32  CreateOptions;
    tui32  CreateDisposition;
    int    rval;
    IRP   *irp;

    if ((irp = devredir_irp_new()) == NULL)
        return -1;

    irp->CompletionId = g_completion_id++;
    irp->completion_type = CID_RMDIR_OR_FILE;
    irp->DeviceId = device_id;

    strncpy(irp->pathname, path, 255);
    devredir_fuse_data_enqueue(irp, fusep);

    //DesiredAccess = DA_DELETE | DA_FILE_READ_ATTRIBUTES | DA_SYNCHRONIZE;
    DesiredAccess = 0x00100080; /* got this value from windows */

    //CreateOptions = CO_FILE_DELETE_ON_CLOSE | CO_FILE_DIRECTORY_FILE |
    //                CO_FILE_SYNCHRONOUS_IO_NONALERT;
    CreateOptions = 0x020; /* got this value from windows */

    //CreateDisposition = CD_FILE_OPEN; // WAS 1
    CreateDisposition = 0x01; /* got this value from windows */

    rval = dev_redir_send_drive_create_request(device_id, path,
                                               DesiredAccess, CreateOptions,
                                               CreateDisposition,
                                               irp->CompletionId);

    return rval;
}

/**
 * Read data from previously opened file
 *
 * @return 0 on success, -1 on failure
 *****************************************************************************/

int APP_CC
devredir_file_read(void *fusep, tui32 DeviceId, tui32 FileId,
                   tui32 Length, tui64 Offset)
{
    struct stream *s;
    IRP           *irp;
    IRP           *new_irp;
    int            bytes;

    xstream_new(s, 1024);

    if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL)
    {
        log_error("no IRP found with FileId = %d", FileId);
        xfuse_devredir_cb_read_file(fusep, NULL, 0);
        xstream_free(s);
        return -1;
    }

    /* create a new IRP for this request */
    if ((new_irp = devredir_irp_clone(irp)) == NULL)
    {
        /* system out of memory */
        xfuse_devredir_cb_read_file(fusep, NULL, 0);
        xstream_free(s);
        return -1;
    }
    new_irp->FileId = 0;
    new_irp->completion_type = CID_READ;
    new_irp->CompletionId = g_completion_id++;
    devredir_fuse_data_enqueue(new_irp, fusep);

    devredir_insert_DeviceIoRequest(s,
                                    DeviceId,
                                    FileId,
                                    new_irp->CompletionId,
                                    IRP_MJ_READ,
                                    0);

    xstream_wr_u32_le(s, Length);
    xstream_wr_u64_le(s, Offset);
    xstream_seek(s, 20);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);
    xstream_free(s);

    return 0;
}

int APP_CC
dev_redir_file_write(void *fusep, tui32 DeviceId, tui32 FileId,
                     const char *buf, int Length, tui64 Offset)
{
    struct stream *s;
    IRP           *irp;
    IRP           *new_irp;
    int            bytes;

    log_debug("DeviceId=%d FileId=%d Length=%d Offset=%lld",
              DeviceId, FileId, Length, Offset);

    xstream_new(s, 1024 + Length);

    if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL)
    {
        log_error("no IRP found with FileId = %d", FileId);
        xfuse_devredir_cb_write_file(fusep, NULL, 0);
        xstream_free(s);
        return -1;
    }

    /* create a new IRP for this request */
    if ((new_irp = devredir_irp_clone(irp)) == NULL)
    {
        /* system out of memory */
        xfuse_devredir_cb_write_file(fusep, NULL, 0);
        xstream_free(s);
        return -1;
    }
    new_irp->FileId = 0;
    new_irp->completion_type = CID_WRITE;
    new_irp->CompletionId = g_completion_id++;
    devredir_fuse_data_enqueue(new_irp, fusep);

    devredir_insert_DeviceIoRequest(s,
                                    DeviceId,
                                    FileId,
                                    new_irp->CompletionId,
                                    IRP_MJ_WRITE,
                                    0);

    xstream_wr_u32_le(s, Length);
    xstream_wr_u64_le(s, Offset);
    xstream_seek(s, 20); /* padding */

    /* now insert real data */
    xstream_copyin(s, buf, Length);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);
    xstream_free(s);

    return 0;
}

/******************************************************************************
**                            FIFO for FUSE_DATA                             **
******************************************************************************/

/**
 * Return FUSE_DATA at the head of the queue without removing it
 *
 * @return FUSE_DATA on success, or NULL on failure
 *****************************************************************************/

FUSE_DATA *APP_CC
devredir_fuse_data_peek(IRP *irp)
{
    log_debug("returning %p", irp->fd_head);
    return irp->fd_head;
}

/**
 * Return oldest FUSE_DATA from queue
 *
 * @return FUSE_DATA on success, NULL on failure
 *****************************************************************************/

FUSE_DATA *APP_CC
devredir_fuse_data_dequeue(IRP *irp)
{
    FUSE_DATA *head;

    if ((irp == NULL) || (irp->fd_head == NULL))
    {
        log_debug("+++ returning NULL");
        return NULL;
    }

    if (irp->fd_head->next == NULL)
    {
        /* only one element in queue */
        head = irp->fd_head;
        irp->fd_head = NULL;
        irp->fd_tail = NULL;
        log_debug("+++ returning FUSE_DATA=%p containing FUSE_INFO=%p",
                  head, head->data_ptr);
        return head;
    }

    /* more than one element in queue */
    head = irp->fd_head;
    irp->fd_head = head->next;
    log_debug("+++ returning FUSE_DATA=%p containing FUSE_INFO=%p",
              head, head->data_ptr);
    return head;
}

/**
 * Insert specified FUSE_DATA at the end of our queue
 *
 * @return 0 on success, -1 on failure
 *****************************************************************************/

int APP_CC
devredir_fuse_data_enqueue(IRP *irp, void *vp)
{
    FUSE_DATA *fd;
    FUSE_DATA *tail;

    if (irp == NULL)
        return -1;

    fd = g_new0(FUSE_DATA, 1);
    if (fd == NULL)
        return -1;

    fd->data_ptr = vp;
    fd->next = NULL;

    if (irp->fd_tail == NULL)
    {
        /* queue is empty, insert at head */
        irp->fd_head = fd;
        irp->fd_tail = fd;
        log_debug("+++ inserted FUSE_DATA=%p containing FUSE_INFO=%p at head",
                  fd, vp);
        return 0;
    }

    /* queue is not empty, insert at tail end */
    tail = irp->fd_tail;
    tail->next = fd;
    irp->fd_tail = fd;
    log_debug("+++ inserted FUSE_DATA=%p containing FUSE_INFO=%p at tail",
              fd, vp);
    return 0;
}

/******************************************************************************
**                           miscellaneous stuff                             **
******************************************************************************/

void APP_CC
devredir_insert_DeviceIoRequest(struct stream *s,
                                tui32 DeviceId,
                                tui32 FileId,
                                tui32 CompletionId,
                                tui32 MajorFunction,
                                tui32 MinorFunction)
{
    /* setup DR_DEVICE_IOREQUEST header */
    xstream_wr_u16_le(s, RDPDR_CTYP_CORE);
    xstream_wr_u16_le(s, PAKID_CORE_DEVICE_IOREQUEST);
    xstream_wr_u32_le(s, DeviceId);
    xstream_wr_u32_le(s, FileId);
    xstream_wr_u32_le(s, CompletionId);
    xstream_wr_u32_le(s, MajorFunction);
    xstream_wr_u32_le(s, MinorFunction);
}

/**
 * Convert / to windows compatible \
 *****************************************************************************/

void APP_CC
devredir_cvt_slash(char *path)
{
    char *cptr = path;

    while (*cptr != 0)
    {
        if (*cptr == '/')
            *cptr = '\\';
        cptr++;
    }
}

void APP_CC
devredir_cvt_to_unicode(char *unicode, const char *path)
{
    char *dest;
    char *src;
    int   rv;
    int   i;

    rv = g_mbstowcs((twchar *) unicode, path, strlen(path));

    /* unicode is typically 4 bytes, but microsoft only uses 2 bytes */

    src  = unicode + sizeof(twchar); /* skip 1st unicode char        */
    dest = unicode + 2;              /* first char already in  place */

    for (i = 1; i < rv; i++)
    {
        *dest++ = *src++;
        *dest++ = *src++;
        src += 2;
    }

    *dest++ = 0;
    *dest++ = 0;
}

void APP_CC
devredir_cvt_from_unicode_len(char *path, char *unicode, int len)
{
    char *dest;
    char *dest_saved;
    char *src;
    int   i;
    int   bytes_to_alloc;
    int   max_bytes;

    bytes_to_alloc = (((len / 2) * sizeof(twchar)) + sizeof(twchar));

    src = unicode;
    dest = g_new0(char, bytes_to_alloc);
    dest_saved = dest;

    for (i = 0; i < len; i += 2)
    {
        *dest++ = *src++;
        *dest++ = *src++;
        dest += 2;
    }
    *dest++ = 0;
    *dest++ = 0;
    *dest++ = 0;
    *dest++ = 0;

    max_bytes = wcstombs(NULL, (wchar_t *) dest_saved, 0);
    if (max_bytes > 0)
    {
        wcstombs(path, (wchar_t *) dest_saved, max_bytes);
        path[max_bytes] = 0;
    }

    g_free(dest_saved);
}

int APP_CC
dev_redir_string_ends_with(char *string, char c)
{
    int len;

    len = strlen(string);
    return (string[len - 1] == c) ? 1 : 0;
}

void APP_CC
devredir_insert_RDPDR_header(struct stream *s, tui16 Component,
                             tui16 PacketId)
{
    xstream_wr_u16_le(s, Component);
    xstream_wr_u16_le(s, PacketId);
}

void APP_CC
devredir_proc_cid_rmdir_or_file(IRP *irp, tui32 IoStatus)
{
    struct stream *s;
    int            bytes;

    if (IoStatus != NT_STATUS_SUCCESS)
    {
        FUSE_DATA *fuse_data = devredir_fuse_data_dequeue(irp);
        if (fuse_data)
        {
            xfuse_devredir_cb_rmdir_or_file(fuse_data->data_ptr, IoStatus);
            free(fuse_data);
        }
        devredir_irp_delete(irp);
        return;
    }

    xstream_new(s, 1024);

    irp->completion_type = CID_RMDIR_OR_FILE_RESP;
    devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId,
                                    irp->CompletionId,
                                    IRP_MJ_SET_INFORMATION, 0);

    xstream_wr_u32_le(s, FileDispositionInformation);
    xstream_wr_u32_le(s, 0); /* length is zero */
    xstream_seek(s, 24);     /* padding        */

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);
    xstream_free(s);

    return;
}

void APP_CC
devredir_proc_cid_rmdir_or_file_resp(IRP *irp, tui32 IoStatus)
{
    FUSE_DATA *fuse_data;

    fuse_data = devredir_fuse_data_dequeue(irp);
    if (fuse_data)
    {
        xfuse_devredir_cb_rmdir_or_file(fuse_data->data_ptr, IoStatus);
        free(fuse_data);
    }

    if (IoStatus != NT_STATUS_SUCCESS)
    {
        devredir_irp_delete(irp);
        return;
    }

    irp->completion_type = CID_CLOSE;
    dev_redir_send_drive_close_request(RDPDR_CTYP_CORE,
                                       PAKID_CORE_DEVICE_IOREQUEST,
                                       irp->DeviceId,
                                       irp->FileId,
                                       irp->CompletionId,
                                       IRP_MJ_CLOSE, 0, 32);
}

void APP_CC
devredir_proc_cid_rename_file(IRP *irp, tui32 IoStatus)
{
    struct stream *s;
    int            bytes;
    int            sblen; /* SetBuffer length */
    int            flen;  /* FileNameLength   */


    if (IoStatus != NT_STATUS_SUCCESS)
    {
        log_debug("rename returned with IoStatus=0x%x", IoStatus);

        FUSE_DATA *fuse_data = devredir_fuse_data_dequeue(irp);
        if (fuse_data)
        {
            xfuse_devredir_cb_rename_file(fuse_data->data_ptr, IoStatus);
            free(fuse_data);
        }
        devredir_irp_delete(irp);
        return;
    }

    /* Path in unicode needs this much space */
    flen = ((g_mbstowcs(NULL, irp->gen_buf, 0) * sizeof(twchar)) / 2) + 2;
    sblen = 6 + flen;

    xstream_new(s, 1024 + flen);

    irp->completion_type = CID_RENAME_FILE_RESP;
    devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId,
                                    irp->CompletionId,
                                    IRP_MJ_SET_INFORMATION, 0);

    xstream_wr_u32_le(s, FileRenameInformation);
    xstream_wr_u32_le(s, sblen);     /* number of bytes after padding */
    xstream_seek(s, 24);             /* padding                       */
    xstream_wr_u8(s, 1);             /* ReplaceIfExists               */
    xstream_wr_u8(s, 0);             /* RootDirectory                 */
    xstream_wr_u32_le(s, flen);      /* FileNameLength                */

    /* filename in unicode */
    devredir_cvt_to_unicode(s->p, irp->gen_buf); /* UNICODE_TODO */
    xstream_seek(s, flen);

    /* send to client */
    bytes = xstream_len(s);
    send_channel_data(g_rdpdr_chan_id, s->data, bytes);
    xstream_free(s);

    return;
}

void APP_CC
devredir_proc_cid_rename_file_resp(IRP *irp, tui32 IoStatus)
{
    FUSE_DATA *fuse_data;

    log_debug("entered");

    fuse_data = devredir_fuse_data_dequeue(irp);
    if (fuse_data)
    {
        xfuse_devredir_cb_rename_file(fuse_data->data_ptr, IoStatus);
        free(fuse_data);
    }

    if (IoStatus != NT_STATUS_SUCCESS)
    {
        devredir_irp_delete(irp);
        return;
    }

    irp->completion_type = CID_CLOSE;
    dev_redir_send_drive_close_request(RDPDR_CTYP_CORE,
                                       PAKID_CORE_DEVICE_IOREQUEST,
                                       irp->DeviceId,
                                       irp->FileId,
                                       irp->CompletionId,
                                       IRP_MJ_CLOSE, 0, 32);
}
