/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#ifndef GUAC_RDP_FS_H
#define GUAC_RDP_FS_H

/**
 * Functions and macros specific to filesystem handling and initialization
 * independent of RDP. The functions here may deal with the filesystem device
 * directly, but their semantics must not deal with RDP protocol messaging.
 * Functions here represent a virtual Windows-style filesystem on top of UNIX
 * system calls and structures, using the guac_rdp_fs structure as a home
 * for common data.
 *
 * @file fs.h 
 */

#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h>
#include <guacamole/user.h>

#include <dirent.h>
#include <stdint.h>

/**
 * The maximum number of file IDs to provide.
 */
#define GUAC_RDP_FS_MAX_FILES 128

/**
 * The maximum number of bytes in a path string.
 */
#define GUAC_RDP_FS_MAX_PATH 4096

/**
 * The maximum number of directories a path may contain.
 */
#define GUAC_RDP_MAX_PATH_DEPTH 64

/**
 * Error code returned when no more file IDs can be allocated.
 */
#define GUAC_RDP_FS_ENFILE -1

/**
 * Error code returned when no such file exists.
 */
#define GUAC_RDP_FS_ENOENT -2

/**
 * Error code returned when the operation required a directory
 * but the file was not a directory.
 */
#define GUAC_RDP_FS_ENOTDIR -3

/**
 * Error code returned when insufficient space exists to complete
 * the operation.
 */
#define GUAC_RDP_FS_ENOSPC -4

/**
 * Error code returned when the operation requires a normal file but
 * a directory was given.
 */
#define GUAC_RDP_FS_EISDIR -5

/**
 * Error code returned when permission is denied.
 */
#define GUAC_RDP_FS_EACCES -6

/**
 * Error code returned when the operation cannot be completed because the
 * file already exists.
 */
#define GUAC_RDP_FS_EEXIST -7

/**
 * Error code returned when invalid parameters were given.
 */
#define GUAC_RDP_FS_EINVAL -8

/**
 * Error code returned when the operation is not implemented.
 */
#define GUAC_RDP_FS_ENOSYS -9

/**
 * Error code returned when the operation is not supported.
 */
#define GUAC_RDP_FS_ENOTSUP -10

/**
 * Converts a UNIX timestamp (seconds since Jan 1, 1970 UTC) to Windows
 * timestamp (100 nanosecond intervals since Jan 1, 1601 UTC).
 */
#define WINDOWS_TIME(t) ((t + ((uint64_t) 11644473600)) * 10000000)

/**
 * An arbitrary file on the virtual filesystem of the Guacamole drive.
 */
typedef struct guac_rdp_fs_file {

    /**
     * The ID of this file.
     */
    int id;

    /**
     * The absolute path, including filename, of this file.
     */
    char* absolute_path;

    /**
     * The real path of this file on the local filesystem.
     */
    char* real_path;

    /**
     * Associated local file descriptor.
     */
    int fd;

    /**
     * Associated directory stream, if any. This field only applies
     * if the file is being used as a directory.
     */
    DIR* dir;

    /**
     * The pattern the check directory contents against, if any.
     */
    char dir_pattern[GUAC_RDP_FS_MAX_PATH];

    /**
     * Bitwise OR of all associated Windows file attributes.
     */
    int attributes;

    /**
     * The size of this file, in bytes.
     */
    uint64_t size;

    /**
     * The time this file was created, as a Windows timestamp.
     */
    uint64_t ctime;

    /**
     * The time this file was last modified, as a Windows timestamp.
     */
    uint64_t mtime;

    /**
     * The time this file was last accessed, as a Windows timestamp.
     */
    uint64_t atime;

    /**
     * The number of bytes written to the file.
     */
    uint64_t bytes_written;

} guac_rdp_fs_file;

/**
 * A virtual filesystem implementing RDP-style operations.
 */
typedef struct guac_rdp_fs {

    /**
     * The Guacamole client associated with the RDP session.
     */
    guac_client* client;

    /**
     * The root of the filesystem.
     */
    char* drive_path;

    /**
     * The number of currently open files.
     */
    int open_files;

    /**
     * Pool of file IDs.
     */
    guac_pool* file_id_pool;

    /**
     * All available file structures.
     */
    guac_rdp_fs_file files[GUAC_RDP_FS_MAX_FILES];
    
    /**
     * If downloads from the remote server to the browser should be disabled.
     */
    int disable_download;
    
    /**
     * If uploads from the browser to the remote server should be disabled.
     */
    int disable_upload;

} guac_rdp_fs;

/**
 * Filesystem information structure.
 */
typedef struct guac_rdp_fs_info {

    /**
     * The number of free blocks available.
     */
    int blocks_available;

    /**
     * The number of blocks in the filesystem.
     */
    int blocks_total;

    /**
     * The number of bytes per block.
     */
    int block_size;

} guac_rdp_fs_info;

/**
 * Allocates a new filesystem given a root path. This filesystem will behave
 * as if it were a network drive.
 *
 * @param client
 *     The guac_client associated with the current RDP session.
 *
 * @param drive_path
 *     The local directory to use as the root directory of the emulated
 *     network drive.
 *
 * @param create_drive_path
 *     Non-zero if the drive path specified should be automatically created if
 *     it does not yet exist, zero otherwise.
 * 
 * @param disable_download
 *     Non-zero if downloads from the remote server to the local browser should
 *     be disabled.
 * 
 * @param disable_upload
 *     Non-zero if uploads from the browser to the remote server should be
 *     disabled.
 *
 * @return
 *     The newly-allocated filesystem.
 */
guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
        int create_drive_path, int disable_download, int disable_upload);

/**
 * Frees the given filesystem.
 *
 * @param fs
 *     The filesystem to free.
 */
void guac_rdp_fs_free(guac_rdp_fs* fs);

/**
 * Creates and exposes a new filesystem guac_object to the given user,
 * providing access to the files within the given RDP filesystem. The
 * allocated guac_object must eventually be freed via guac_user_free_object().
 *
 * @param fs
 *     The RDP filesystem object to expose.
 *
 * @param user
 *     The user that the RDP filesystem should be exposed to.
 *
 * @return
 *     A new Guacamole filesystem object, configured to use RDP for uploading
 *     and downloading files.
 */
guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user);

/**
 * Allocates a new filesystem guac_object for the given user, returning the
 * resulting guac_object. This function is provided for convenience, as it is
 * can be used as the callback for guac_client_foreach_user() or
 * guac_client_for_owner(). Note that this guac_object will be tracked
 * internally by libguac, will be provided to us in the parameters of handlers
 * related to that guac_object, and will automatically be freed when the
 * associated guac_user is freed, so the return value of this function can
 * safely be ignored.
 *
 * If either the given user or the given filesystem are NULL, then this
 * function has no effect.
 *
 * @param user
 *     The use to expose the filesystem to, or NULL if nothing should be
 *     exposed.
 *
 * @param data
 *     A pointer to the guac_rdp_fs instance to expose to the given user, or
 *     NULL if nothing should be exposed.
 *
 * @return
 *     The guac_object allocated for the newly-exposed filesystem, or NULL if
 *     no filesystem object could be allocated.
 */
void* guac_rdp_fs_expose(guac_user* user, void* data);

/**
 * Converts the given relative path to an absolute path based on the given
 * parent path. If the path cannot be converted, non-zero is returned.
 *
 * @param parent
 *     The parent directory of the relative path.
 *
 * @param rel_path
 *     The relative path to convert.
 *
 * @return
 *     Zero if the path was converted successfully, non-zero otherwise.
 */
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path,
        char* abs_path);

/**
 * Translates the given errno error code to a GUAC_RDP_FS error code.
 *
 * @param err
 *     The error code, as returned within errno by a system call.
 *
 * @return
 *     A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE,
 *     GUAC_RDP_FS_ENOENT, etc.
 */
int guac_rdp_fs_get_errorcode(int err);

/**
 * Translates the given GUAC_RDP_FS error code to an RDPDR status code.
 *
 * @param err
 *     A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE,
 *     GUAC_RDP_FS_ENOENT, etc.
 *
 * @return
 *     A status code corresponding to the given error code that an
 *     implementation of the RDPDR channel can understand.
 */
int guac_rdp_fs_get_status(int err);

/**
 * Opens the given file, returning the a new file ID, or an error code less
 * than zero if an error occurs. The given path MUST be absolute, and will be
 * translated to be relative to the drive path of the simulated filesystem.
 *
 * @param fs
 *     The filesystem to use when opening the file.
 *
 * @param path
 *     The absolute path to the file within the simulated filesystem.
 *
 * @param access
 *     A bitwise-OR of various RDPDR access flags, such as GENERIC_ALL or
 *     GENERIC_WRITE. This value will ultimately be translated to a standard
 *     O_RDWR, O_WRONLY, etc. value when opening the real file on the local
 *     filesystem.
 *
 * @param file_attributes
 *     The attributes to apply to the file, if created. This parameter is
 *     currently ignored, and has no effect.
 *
 * @param create_disposition
 *     Any one of several RDPDR file creation dispositions, such as
 *     FILE_CREATE, FILE_OPEN_IF, etc. The creation disposition dictates
 *     whether a new file should be created, whether the file can already
 *     exist, whether existing contents should be truncated, etc.
 *
 * @param create_options
 *     A bitwise-OR of various RDPDR options dictating how a file is to be
 *     created. Currently only one option is implemented, FILE_DIRECTORY_FILE,
 *     which specifies that the new file must be a directory.
 *
 * @return
 *     A new file ID, which will always be a positive value, or an error code
 *     if an error occurs. All error codes are negative values and correspond
 *     to GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
        int access, int file_attributes, int create_disposition,
        int create_options);

/**
 * Reads up to the given length of bytes from the given offset within the
 * file having the given ID. Returns the number of bytes read, zero on EOF,
 * and an error code if an error occurs.
 *
 * @param fs
 *     The filesystem containing the file from which data is to be read.
 *
 * @param file_id
 *     The ID of the file to read data from, as returned by guac_rdp_fs_open().
 *
 * @param offset
 *     The byte offset within the file to start reading from.
 *
 * @param buffer
 *     The buffer to fill with data from the file.
 *
 * @param length
 *     The maximum number of bytes to read from the file.
 *
 * @return
 *     The number of bytes actually read, zero on EOF, or an error code if an
 *     error occurs. All error codes are negative values and correspond to
 *     GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset,
        void* buffer, int length);

/**
 * Writes up to the given length of bytes from the given offset within the
 * file having the given ID. Returns the number of bytes written, and an
 * error code if an error occurs.
 *
 * @param fs
 *     The filesystem containing the file to which data is to be written.
 *
 * @param file_id
 *     The ID of the file to write data to, as returned by guac_rdp_fs_open().
 *
 * @param offset
 *     The byte offset within the file to start writinging at.
 *
 * @param buffer
 *     The buffer containing the data to write.
 *
 * @param length
 *     The maximum number of bytes to write to the file.
 *
 * @return
 *     The number of bytes actually written, or an error code if an error
 *     occurs. All error codes are negative values and correspond to
 *     GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset,
        void* buffer, int length);

/**
 * Renames (moves) the file with the given ID to the new path specified.
 * Returns zero on success, or an error code if an error occurs.
 *
 * @param fs
 *     The filesystem containing the file to rename.
 *
 * @param file_id
 *     The ID of the file to rename, as returned by guac_rdp_fs_open().
 *
 * @param new_path
 *     The absolute path to move the file to.
 *
 * @return
 *     Zero if the rename succeeded, or an error code if an error occurs. All
 *     error codes are negative values and correspond to GUAC_RDP_FS constants,
 *     such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id,
        const char* new_path);

/**
 * Deletes the file with the given ID.
 *
 * @param fs
 *     The filesystem containing the file to delete.
 *
 * @param file_id
 *     The ID of the file to delete, as returned by guac_rdp_fs_open().
 *
 * @return
 *     Zero if deletion succeeded, or an error code if an error occurs. All
 *     error codes are negative values and correspond to GUAC_RDP_FS constants,
 *     such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id);

/**
 * Truncates the file with the given ID to the given length (in bytes), which
 * may be larger.
 *
 * @param fs
 *     The filesystem containing the file to truncate.
 *
 * @param file_id
 *     The ID of the file to truncate, as returned by guac_rdp_fs_open().
 *
 * @param length
 *     The new length of the file, in bytes. Despite being named "truncate",
 *     this new length may be larger.
 *
 * @return
 *     Zero if truncation succeeded, or an error code if an error occurs. All
 *     error codes are negative values and correspond to GUAC_RDP_FS constants,
 *     such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length);

/**
 * Frees the given file ID, allowing future open operations to reuse it.
 *
 * @param fs
 *     The filesystem containing the file to close.
 *
 * @param file_id
 *     The ID of the file to close, as returned by guac_rdp_fs_open().
 */
void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id);

/**
 * Given an arbitrary path, returns a pointer to the first character following
 * the last path separator in the path (the basename of the path). For example,
 * given "/foo/bar/baz" or "\foo\bar\baz", this function would return a pointer
 * to "baz".
 *
 * @param path
 *     The path to determine the basename of.
 *
 * @return
 *     A pointer to the first character of the basename within the path.
 */
const char* guac_rdp_fs_basename(const char* path);

/**
 * Given an arbitrary path, which may contain ".." and ".", creates an
 * absolute path which does NOT contain ".." or ".". The given path MUST
 * be absolute.
 *
 * @param path
 *     The absolute path to normalize.
 *
 * @param abs_path
 *     The buffer to populate with the normalized path. The normalized path
 *     will not contain relative path components like ".." or ".".
 *
 * @return
 *     Zero if normalization succeeded, non-zero otherwise.
 */
int guac_rdp_fs_normalize_path(const char* path, char* abs_path);

/**
 * Given a parent path and a relative path, produces a normalized absolute
 * path.
 *
 * @param parent 
 *     The absolute path of the parent directory of the relative path.
 *
 * @param rel_path
 *     The relative path to convert.
 *
 * @param abs_path
 *     The buffer to populate with the absolute, normalized path. The
 *     normalized path will not contain relative path components like ".." or
 *     ".".
 *
 * @return
 *     Zero if conversion succeeded, non-zero otherwise.
 */
int guac_rdp_fs_convert_path(const char* parent, const char* rel_path,
        char* abs_path);

/**
 * Returns the next filename within the directory having the given file ID,
 * or NULL if no more files.
 *
 * @param fs
 *     The filesystem containing the file to read directory entries from.
 *
 * @param file_id
 *     The ID of the file to read directory entries from, as returned by
 *     guac_rdp_fs_open().
 *
 * @return
 *     The name of the next filename within the directory, or NULL if the last
 *     file in the directory has already been returned by a previous call.
 */
const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id);

/**
 * Returns the file having the given ID, or NULL if no such file exists.
 *
 * @param fs
 *     The filesystem containing the desired file.
 *
 * @param file_id
 *     The ID of the desired, as returned by guac_rdp_fs_open().
 *
 * @return
 *     The file having the given ID, or NULL is no such file exists.
 */
guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id);

/**
 * Returns whether the given filename matches the given pattern. The pattern
 * given is a shell wildcard pattern as accepted by the POSIX fnmatch()
 * function. Backslashes will be interpreted as literal backslashes, not
 * escape characters.
 *
 * @param filename
 *     The filename to check
 *
 * @param pattern
 *     The pattern to check the filename against.
 *
 * @return
 *     Non-zero if the pattern matches, zero otherwise.
 */
int guac_rdp_fs_matches(const char* filename, const char* pattern);

/**
 * Populates the given structure with information about the filesystem,
 * particularly the amount of space available.
 *
 * @param fs
 *     The filesystem to obtain information from.
 *
 * @param info
 *     The guac_rdp_fs_info structure to populate.
 *
 * @return 
 *     Zero if information retrieval succeeded, or an error code if an error
 *     occurs. All error codes are negative values and correspond to
 *     GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT.
 */
int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info);

/**
 * Concatenates the given filename with the given path, separating the two
 * with a single forward slash. The full result must be no more than
 * GUAC_RDP_FS_MAX_PATH bytes long, counting null terminator.
 *
 * @param fullpath
 *     The buffer to store the result within. This buffer must be at least
 *     GUAC_RDP_FS_MAX_PATH bytes long.
 *
 * @param path
 *     The path to append the filename to.
 *
 * @param filename
 *     The filename to append to the path.
 *
 * @return
 *     Non-zero if the filename is valid and was successfully appended to the
 *     path, zero otherwise.
 */
int guac_rdp_fs_append_filename(char* fullpath, const char* path,
        const char* filename);

#endif

