<commit_before>/*  vdagent file xfers code

    Copyright 2013 Red Hat, Inc.

    Red Hat Authors:
    <NAME> <<EMAIL>>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or   
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <spice/vd_agent.h>
#include <glib.h>

#include "vdagentd-proto.h"
#include "vdagent-file-xfers.h"

struct vdagent_file_xfers {
    GHashTable *xfers;
    struct udscs_connection *vdagentd;
    int debug;
};

typedef struct AgentFileXferTask {
    uint32_t                       id;
    int                            file_fd;
    uint64_t                       read_bytes;
    char                           *file_name;
    uint64_t                       file_size;
    int                            debug;
} AgentFileXferTask;

static void vdagent_file_xfer_task_free(gpointer data)
{
    AgentFileXferTask *task = data;

    g_return_if_fail(task != NULL);

    if (task->file_fd > 0) {
        syslog(LOG_ERR, "file-xfer: Removing task %u and file %s due to error",
               task->id, task->file_name);
        close(task->file_fd);
        unlink(task->file_name);
    } else if (task->debug)
        syslog(LOG_DEBUG, "file-xfer: Removing task %u %s",
               task->id, task->file_name);

    g_free(task->file_name);
    g_free(task);
}

struct vdagent_file_xfers *vdagent_file_xfers_create(
    struct udscs_connection *vdagentd, int debug)
{
    struct vdagent_file_xfers *xfers;

    xfers = g_malloc(sizeof(*xfers));
    xfers->xfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                         NULL, vdagent_file_xfer_task_free);
    xfers->vdagentd = vdagentd;
    xfers->debug = debug;

    return xfers;
}

void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfers)
{
    g_hash_table_destroy(xfers->xfers);
    g_free(xfers);
}

AgentFileXferTask *vdagent_file_xfers_get_task(
    struct vdagent_file_xfers *xfers, uint32_t id)
{
    AgentFileXferTask *task;

    task = g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(id));
    if (task == NULL)
        syslog(LOG_ERR, "file-xfer: error can not find task %u", id);

    return task;
}

/* Parse start message then create a new file xfer task */
static AgentFileXferTask *vdagent_parse_start_msg(
    VDAgentFileXferStartMessage *msg)
{
    GKeyFile *keyfile = NULL;
    AgentFileXferTask *task = NULL;
    GError *error = NULL;

    keyfile = g_key_file_new();
    if (g_key_file_load_from_data(keyfile,
                                  (const gchar *)msg->data,
                                  -1,
                                  G_KEY_FILE_NONE, &error) == FALSE) {
        syslog(LOG_ERR, "file-xfer: failed to load keyfile: %s",
               error->message);
        goto error;
    }
    task = g_new0(AgentFileXferTask, 1);
    task->id = msg->id;
    task->file_name = g_key_file_get_string(
        keyfile, "vdagent-file-xfer", "name", &error);
    if (error) {
        syslog(LOG_ERR, "file-xfer: failed to parse filename: %s",
               error->message);
        goto error;
    }
    task->file_size = g_key_file_get_uint64(
        keyfile, "vdagent-file-xfer", "size", &error);
    if (error) {
        syslog(LOG_ERR, "file-xfer: failed to parse filesize: %s",
               error->message);
        goto error;
    }

    g_key_file_free(keyfile);
    return task;

error:
    g_clear_error(&error);
    if (task)
        vdagent_file_xfer_task_free(task);
    if (keyfile)
        g_key_file_free(keyfile);
    return NULL;
}

void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
    VDAgentFileXferStartMessage *msg)
{
    AgentFileXferTask *task;
    char *path = NULL, *file_path = NULL;
    const gchar *desktop;
    struct stat st;
    int i;

    task = vdagent_parse_start_msg(msg);
    if (task == NULL) {
        goto error;
    }

    task->debug = xfers->debug;
    desktop = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
    if (desktop == NULL) {
        goto error;
    }

    file_path = g_build_filename(desktop, task->file_name, NULL);
    path = g_strdup(file_path);
    for (i = 0; i < 64 && (stat(path, &st) == 0 || errno != ENOENT); i++) {
        g_free(path);
        path = g_strdup_printf("%s (%d)", file_path, i + 1);
    }
    g_free(file_path);
    if (i == 64) {
        syslog(LOG_ERR, "file-xfer: more then 63 copies of %s exist?",
               task->file_name);
        goto error;
    }

    task->file_fd = open(path, O_CREAT | O_WRONLY, 0644);
    if (task->file_fd == -1) {
        syslog(LOG_ERR, "file-xfer: failed to create file %s: %s",
               path, strerror(errno));
        goto error;
    }

    if (ftruncate(task->file_fd, task->file_size) < 0) {
        syslog(LOG_ERR, "file-xfer: err reserving %"PRIu64" bytes for %s: %s",
               task->file_size, path, strerror(errno));
        goto error;
    }

    g_hash_table_insert(xfers->xfers, GUINT_TO_POINTER(msg->id), task);

    if (xfers->debug)
        syslog(LOG_DEBUG, "file-xfer: Adding task %u %s %"PRIu64" bytes",
               task->id, path, task->file_size);

    udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
                msg->id, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA, NULL, 0);
    g_free(path);
    return ;

error:
    udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
                msg->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
    if (task)
        vdagent_file_xfer_task_free(task);
    g_free(path);
}

void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers,
    VDAgentFileXferStatusMessage *msg)
{
    AgentFileXferTask *task;

    task = vdagent_file_xfers_get_task(xfers, msg->id);
    if (!task)
        return;

    switch (msg->result) {
    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
        syslog(LOG_ERR, "file-xfer: task %u %s received unexpected 0 response",
               task->id, task->file_name);
        break;
    default:
        /* Cancel or Error, remove this task */
        g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
    }
}

void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers,
    VDAgentFileXferDataMessage *msg)
{
    AgentFileXferTask *task;
    int len;

    task = vdagent_file_xfers_get_task(xfers, msg->id);
    if (!task)
        return;

    len = write(task->file_fd, msg->data, msg->size);
    if (len == -1) {
        syslog(LOG_ERR, "file-xfer: error writing %s: %s", task->file_name,
               strerror(errno));
        /* TODO: close, cancel dnd */
        return ;
    }

    task->read_bytes += msg->size;
    if (task->read_bytes >= task->file_size) {
        if (task->read_bytes == task->file_size) {
            if (xfers->debug)
                syslog(LOG_DEBUG, "file-xfer: task %u %s has completed",
                       task->id, task->file_name);
            close(task->file_fd);
            task->file_fd = -1;
        } else
            syslog(LOG_ERR, "file-xfer: error received too much data");
        g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
    }
}
<commit_msg>file-xfers: Check for duplicate task ids<commit_after>/*  vdagent file xfers code

    Copyright 2013 Red Hat, Inc.

    Red Hat Authors:
    <NAME> <<EMAIL>>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or   
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <spice/vd_agent.h>
#include <glib.h>

#include "vdagentd-proto.h"
#include "vdagent-file-xfers.h"

struct vdagent_file_xfers {
    GHashTable *xfers;
    struct udscs_connection *vdagentd;
    int debug;
};

typedef struct AgentFileXferTask {
    uint32_t                       id;
    int                            file_fd;
    uint64_t                       read_bytes;
    char                           *file_name;
    uint64_t                       file_size;
    int                            debug;
} AgentFileXferTask;

static void vdagent_file_xfer_task_free(gpointer data)
{
    AgentFileXferTask *task = data;

    g_return_if_fail(task != NULL);

    if (task->file_fd > 0) {
        syslog(LOG_ERR, "file-xfer: Removing task %u and file %s due to error",
               task->id, task->file_name);
        close(task->file_fd);
        unlink(task->file_name);
    } else if (task->debug)
        syslog(LOG_DEBUG, "file-xfer: Removing task %u %s",
               task->id, task->file_name);

    g_free(task->file_name);
    g_free(task);
}

struct vdagent_file_xfers *vdagent_file_xfers_create(
    struct udscs_connection *vdagentd, int debug)
{
    struct vdagent_file_xfers *xfers;

    xfers = g_malloc(sizeof(*xfers));
    xfers->xfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                         NULL, vdagent_file_xfer_task_free);
    xfers->vdagentd = vdagentd;
    xfers->debug = debug;

    return xfers;
}

void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfers)
{
    g_hash_table_destroy(xfers->xfers);
    g_free(xfers);
}

AgentFileXferTask *vdagent_file_xfers_get_task(
    struct vdagent_file_xfers *xfers, uint32_t id)
{
    AgentFileXferTask *task;

    task = g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(id));
    if (task == NULL)
        syslog(LOG_ERR, "file-xfer: error can not find task %u", id);

    return task;
}

/* Parse start message then create a new file xfer task */
static AgentFileXferTask *vdagent_parse_start_msg(
    VDAgentFileXferStartMessage *msg)
{
    GKeyFile *keyfile = NULL;
    AgentFileXferTask *task = NULL;
    GError *error = NULL;

    keyfile = g_key_file_new();
    if (g_key_file_load_from_data(keyfile,
                                  (const gchar *)msg->data,
                                  -1,
                                  G_KEY_FILE_NONE, &error) == FALSE) {
        syslog(LOG_ERR, "file-xfer: failed to load keyfile: %s",
               error->message);
        goto error;
    }
    task = g_new0(AgentFileXferTask, 1);
    task->id = msg->id;
    task->file_name = g_key_file_get_string(
        keyfile, "vdagent-file-xfer", "name", &error);
    if (error) {
        syslog(LOG_ERR, "file-xfer: failed to parse filename: %s",
               error->message);
        goto error;
    }
    task->file_size = g_key_file_get_uint64(
        keyfile, "vdagent-file-xfer", "size", &error);
    if (error) {
        syslog(LOG_ERR, "file-xfer: failed to parse filesize: %s",
               error->message);
        goto error;
    }

    g_key_file_free(keyfile);
    return task;

error:
    g_clear_error(&error);
    if (task)
        vdagent_file_xfer_task_free(task);
    if (keyfile)
        g_key_file_free(keyfile);
    return NULL;
}

void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
    VDAgentFileXferStartMessage *msg)
{
    AgentFileXferTask *task;
    char *path = NULL, *file_path = NULL;
    const gchar *desktop;
    struct stat st;
    int i;

    if (g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(msg->id))) {
        syslog(LOG_ERR, "file-xfer: error id %u already exists, ignoring!",
               msg->id);
        return;
    }

    task = vdagent_parse_start_msg(msg);
    if (task == NULL) {
        goto error;
    }

    task->debug = xfers->debug;
    desktop = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
    if (desktop == NULL) {
        goto error;
    }

    file_path = g_build_filename(desktop, task->file_name, NULL);
    path = g_strdup(file_path);
    for (i = 0; i < 64 && (stat(path, &st) == 0 || errno != ENOENT); i++) {
        g_free(path);
        path = g_strdup_printf("%s (%d)", file_path, i + 1);
    }
    g_free(file_path);
    if (i == 64) {
        syslog(LOG_ERR, "file-xfer: more then 63 copies of %s exist?",
               task->file_name);
        goto error;
    }

    task->file_fd = open(path, O_CREAT | O_WRONLY, 0644);
    if (task->file_fd == -1) {
        syslog(LOG_ERR, "file-xfer: failed to create file %s: %s",
               path, strerror(errno));
        goto error;
    }

    if (ftruncate(task->file_fd, task->file_size) < 0) {
        syslog(LOG_ERR, "file-xfer: err reserving %"PRIu64" bytes for %s: %s",
               task->file_size, path, strerror(errno));
        goto error;
    }

    g_hash_table_insert(xfers->xfers, GUINT_TO_POINTER(msg->id), task);

    if (xfers->debug)
        syslog(LOG_DEBUG, "file-xfer: Adding task %u %s %"PRIu64" bytes",
               task->id, path, task->file_size);

    udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
                msg->id, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA, NULL, 0);
    g_free(path);
    return ;

error:
    udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
                msg->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
    if (task)
        vdagent_file_xfer_task_free(task);
    g_free(path);
}

void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers,
    VDAgentFileXferStatusMessage *msg)
{
    AgentFileXferTask *task;

    task = vdagent_file_xfers_get_task(xfers, msg->id);
    if (!task)
        return;

    switch (msg->result) {
    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
        syslog(LOG_ERR, "file-xfer: task %u %s received unexpected 0 response",
               task->id, task->file_name);
        break;
    default:
        /* Cancel or Error, remove this task */
        g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
    }
}

void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers,
    VDAgentFileXferDataMessage *msg)
{
    AgentFileXferTask *task;
    int len;

    task = vdagent_file_xfers_get_task(xfers, msg->id);
    if (!task)
        return;

    len = write(task->file_fd, msg->data, msg->size);
    if (len == -1) {
        syslog(LOG_ERR, "file-xfer: error writing %s: %s", task->file_name,
               strerror(errno));
        /* TODO: close, cancel dnd */
        return ;
    }

    task->read_bytes += msg->size;
    if (task->read_bytes >= task->file_size) {
        if (task->read_bytes == task->file_size) {
            if (xfers->debug)
                syslog(LOG_DEBUG, "file-xfer: task %u %s has completed",
                       task->id, task->file_name);
            close(task->file_fd);
            task->file_fd = -1;
        } else
            syslog(LOG_ERR, "file-xfer: error received too much data");
        g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
    }
}

{'fraction_non_alphanumeric': 0.11391691208722352, 'fraction_numerical': 0.005388808822607932, 'mean_word_length': 2.489287275907302, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 172, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5253132', 'n_tokens_mistral': 5692, 'n_tokens_neox': 5532, 'n_words': 1266}