/********************************************************************************/
/*                                                                              */
/*                            CUSE TPM                                          */
/*                     IBM Thomas J. Watson Research Center                     */
/*                                                                              */
/* (c) Copyright IBM Corporation 2014-2015.					*/
/*										*/
/* All rights reserved.								*/
/* 										*/
/* Redistribution and use in source and binary forms, with or without		*/
/* modification, are permitted provided that the following conditions are	*/
/* met:										*/
/* 										*/
/* Redistributions of source code must retain the above copyright notice,	*/
/* this list of conditions and the following disclaimer.			*/
/* 										*/
/* Redistributions in binary form must reproduce the above copyright		*/
/* notice, this list of conditions and the following disclaimer in the		*/
/* documentation and/or other materials provided with the distribution.		*/
/* 										*/
/* Neither the names of the IBM Corporation nor the names of its		*/
/* contributors may be used to endorse or promote products derived from		*/
/* this software without specific prior written permission.			*/
/* 										*/
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS		*/
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT		*/
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR	*/
/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT		*/
/* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,	*/
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT		*/
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,	*/
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY	*/
/* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT		*/
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE	*/
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.		*/
/********************************************************************************/

/*
 * Authors:
 *     Eric Richter, erichte@us.ibm.com
 *     Stefan Berger, stefanb@us.ibm.com
 *     David Safford, safford@us.ibm.com
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include <errno.h>
#include <arpa/inet.h>
#include <dirent.h>

#include <fuse/cuse_lowlevel.h>

#include <glib.h>

#include <libtpms/tpm_library.h>
#include <libtpms/tpm_tis.h>
#include <libtpms/tpm_error.h>
#include <libtpms/tpm_memory.h>

#include "swtpm.h"
#include "common.h"
#include "tpmstate.h"
#include "pidfile.h"
#include "locality.h"
#include "logging.h"
#include "tpm_ioctl.h"
#include "swtpm_nvstore.h"
#include "tpmlib.h"
#include "main.h"
#include "utils.h"
#include "threadpool.h"
#include "seccomp_profile.h"
#include "options.h"
#include "capabilities.h"
#include "swtpm_utils.h"

/* maximum size of request buffer */
#define TPM_REQ_MAX 4096

/* version of the TPM (1.2 or 2) */
static TPMLIB_TPMVersion tpmversion;

/* buffer containing the TPM request */
static unsigned char *ptm_request;

/* buffer containing the TPM response; protected by file_ops_lock and thread_busy_lock */
static unsigned char *ptm_response;

/* offset from where to read from; reset when ptm_response is set */
static size_t ptm_read_offset;

/* the sizes of the data in the buffers */
static uint32_t ptm_req_len, ptm_res_len, ptm_res_tot;

/* locality applied to TPM commands */
static TPM_MODIFIER_INDICATOR locality;

/* whether the TPM is running (TPM_Init was received); protected by file_ops_lock */
static bool tpm_running;

/* flags on how to handle locality */
static uint32_t locality_flags;

/* the fuse_session that we will signal an exit to to exit the prg. */
static struct fuse_session *ptm_fuse_session;

/* last command sent to the TPM */
static uint32_t g_lastCommand = TPM_ORDINAL_NONE;

/* TPM2_Shutdown() will NOT be sent by swtpm */
static bool g_disable_auto_shutdown = false;

/* whether to defer locking NVRAM storage due to incoming migration */
static bool g_incoming_migration;

/* whether NVRAM storage is currently locked */
static bool g_storage_locked;

/* whether to release the lock on outgoing migration */
static bool g_release_lock_outgoing;

/* how many times to retry locking; use for fallback after releasing
   the lock on outgoing migration. */
static unsigned int g_locking_retries;
#define DEFAULT_LOCKING_RETRIES  300 /* 300 * 10ms */

/* the JSON profile for the TPM 2 */
static char *g_json_profile;

#if GLIB_MAJOR_VERSION >= 2
# if GLIB_MINOR_VERSION >= 32

static GMutex file_ops_lock;
#  define FILE_OPS_LOCK &file_ops_lock

# else

static GMutex *file_ops_lock;
#  define FILE_OPS_LOCK file_ops_lock

# endif
#else

#error Unsupported glib version

#endif

struct cuse_param {
    char *runas;
    char *chroot;
    char *logging;
    char *keydata;
    char *migkeydata;
    char *piddata;
    char *tpmstatedata;
    char *localitydata;
    char *seccompdata;
    char *migrationdata;
    char *profiledata;
    unsigned int seccomp_action;
    char *flagsdata;
    uint16_t startupType;
};

/* single message to send to the worker thread */
static struct thread_message g_msg;

struct stateblob {
    uint8_t type;
    uint8_t *data;
    uint32_t length;
    TPM_BOOL is_encrypted;
};

typedef struct stateblob_desc {
    uint32_t blobtype;
    TPM_BOOL decrypt;
    TPM_BOOL is_encrypted;
    unsigned char *data;
    uint32_t data_length;
} stateblob_desc;

typedef enum tx_state_type {
    TX_STATE_CLOSED = 0,
    TX_STATE_RW_COMMAND = 1,
    TX_STATE_SET_STATE_BLOB = 2,
    TX_STATE_GET_STATE_BLOB = 3,
} tx_state_type;

typedef struct transfer_state {
    tx_state_type state;
    /* while in TX_STATE_GET/SET_STATEBLOB */
    uint32_t blobtype;
    TPM_BOOL blob_is_encrypted;
    /* while in TX_STATE_GET */
    uint32_t offset;
} transfer_state;

typedef struct TPM_Response_Header {
    uint16_t tag;
    uint32_t paramSize;
    uint32_t returnCode;
} __attribute__ ((packed)) TPM_Response_Header;

/*********************************** data *************************************/

static void usage(FILE *file, const char *prgname, const char *iface)
{
    fprintf(file,
    "usage: %s %s [options]\n"
    "\n"
    "The following options are supported:\n"
    "\n"
    "-n NAME|--name=NAME :  device name (mandatory)\n"
    "-M MAJ|--maj=MAJ    :  device major number\n"
    "-m MIN|--min=MIN    :  device minor number\n"
    "--key file=<path>|fd=<fd>[,mode=aes-cbc|aes-256-cbc][,format=hex|binary][,remove=[true|false]]\n"
    "                    :  use an AES key for the encryption of the TPM's state\n"
    "                       files; use the given mode for the block encryption;\n"
    "                       the key is to be provided as a hex string or in binary\n"
    "                       format; the keyfile can be automatically removed using\n"
    "                       the remove parameter\n"
    "--key pwdfile=<path>|pwdfd=<fd>[,mode=aes-cbc|aes-256-cbc][,remove=[true|false]][,kdf=sha512|pbkdf2]\n"
    "                    :  provide a passphrase in a file; the AES key will be\n"
    "                       derived from this passphrase; default kdf is PBKDF2\n"
    "--locality [reject-locality-4][,allow-set-locality]\n"
    "                    :  reject-locality-4: reject any command in locality 4\n"
    "                       allow-set-locality: accept SetLocality command\n"
    "--migration-key file=<path>|fd=<fd>[,mode=aes-cbc|aes-256-cbc][,format=hex|binary][,remove=[true|false]]\n"
    "                    :  use an AES key for the encryption of the TPM's state\n"
    "                       when it is retrieved from the TPM via ioctls;\n"
    "                       Setting this key ensures that the TPM's state will always\n"
    "                       be encrypted when migrated\n"
    "--migration-key pwdfile=<path>|pwdfd=<fd>[,mode=aes-cbc|aes-256-cbc][,remove=[true|false]][,kdf=sha512|pbkdf2]\n"
    "                    :  provide a passphrase in a file; the AES key will be\n"
    "                       derived from this passphrase; default kdf is PBKDF2\n"
    "--log file=<path>|fd=<filedescriptor>[,level=n][,prefix=<prefix>][,truncate]\n"
    "                    :  write the TPM's log into the given file rather than\n"
    "                       to the console; provide '-' for path to avoid logging\n"
    "                       log level 5 and higher will enable libtpms logging;\n"
    "                       all logged output will be prefixed with prefix;\n"
    "                       the log file can be reset (truncate)\n"
    "--pid file=<path>|fd=<filedescriptor>\n"
    "                    :  write the process ID into the given file\n"
    "--tpmstate dir=<dir>|backend-uri=<uri>[,mode=0...][,lock]\n"
    "                    :  set the directory or uri where the TPM's state will be written\n"
    "                       into; the TPM_PATH environment variable can be used\n"
    "                       instead of dir option;\n"
    "                       mode allows a user to set the file mode bits of the state\n"
    "                       files; the default mode is 0640;\n"
    "                       lock enables file-locking by the storage backend;\n"
    "--flags [not-need-init][,startup-clear|startup-state|startup-deactivated|startup-none][,disable-auto-shutdown]\n"
    "                    :  not-need-init: commands can be sent without needing to\n"
    "                       send an INIT via control channel;\n"
    "                       startup-...: send Startup command with this type;\n"
    "                       disable-auto-shutdown disables automatic sending of\n"
    "                       TPM2_Shutdown before TPM 2 reset or swtpm termination;\n"
    "-r|--runas <user>   :  after creating the CUSE device, change to the given\n"
    "                       user\n"
    "-R|--chroot <path>  :  chroot to the given directory at startup\n"
    "--tpm2              :  choose TPM2 functionality\n"
#ifdef WITH_SECCOMP
# ifndef SCMP_ACT_LOG
    "--seccomp action=none|kill\n"
# else
    "--seccomp action=none|kill|log\n"
# endif
    "                    :  Choose the action of the seccomp profile when a\n"
    "                       blacklisted syscall is executed; default is kill\n"
#endif
    "--migration [incoming][,release-lock-outgoing]\n"
    "                    : Incoming migration defers locking of storage backend\n"
    "                      until the TPM state is received; release-lock-outgoing\n"
    "                      releases the storage lock on outgoing migration\n"
    "--print-capabilities : print capabilities and terminate\n"
    "--print-states      : print existing TPM states and terminate\n"
    "--profile name=<name>|profile=<json-profile>|file=<filename>|fd=<fd>[,remove-disabled=check|fips-host]\n"
    "                    : Set a profile on the TPM 2\n"
    "                      remove-disabled: On the 'custom' profile remove algorithms\n"
    "                      disabled by FIPS mode in OpenSSL; use 'check' to test the\n"
    "                      algorithms first\n"
    "--print-profiles\n"
    "                    : print all profiles supported by libtpms\n"
    "--print-info <info flags>\n"
    "                    : print information about the TPM and profiles and exit\n"
    "-h|--help           : display this help screen and terminate\n"
    "\n",
    prgname, iface);
}
static TPM_RESULT
ptm_io_getlocality(TPM_MODIFIER_INDICATOR *loc,
                   uint32_t tpmnum SWTPM_ATTR_UNUSED)
{
    *loc = locality;
    return TPM_SUCCESS;
}

static struct libtpms_callbacks cbs = {
    .sizeOfStruct           = sizeof(struct libtpms_callbacks),
    .tpm_nvram_init         = SWTPM_NVRAM_Init,
    .tpm_nvram_loaddata     = SWTPM_NVRAM_LoadData,
    .tpm_nvram_storedata    = SWTPM_NVRAM_StoreData,
    .tpm_nvram_deletename   = SWTPM_NVRAM_DeleteName,
    .tpm_io_getlocality     = ptm_io_getlocality,
};

/* the current state the transfer interface is in */
static transfer_state tx_state;

/* function prototypes */
static void ptm_cleanup(void);

/************************* NVRAM locking ************************************/

/* ensure that the NVRAM is locked; returns false in case of failure */
static bool ensure_locked_storage(void)
{
    TPM_RESULT res;

    if (g_storage_locked)
        return true;

    /* if NVRAM hasn't been initialized yet locking may need to be retried */
    res = SWTPM_NVRAM_Lock_Storage(g_locking_retries);
    if (res == TPM_RETRY)
        return true;
    if (res != TPM_SUCCESS)
        return false;

    g_storage_locked = true;
    g_incoming_migration = false;
    g_locking_retries = 0;

    return true;
}

static void unlock_nvram(unsigned int locking_retries)
{
    SWTPM_NVRAM_Unlock();

    g_storage_locked = false;
    g_locking_retries = locking_retries;
}

/************************* cached stateblob *********************************/

static stateblob_desc cached_stateblob;

/*
 * cached_stateblob_is_loaded: is the stateblob with the given properties
 *                             the one in the cache?
 */
static bool cached_stateblob_is_loaded(uint32_t blobtype,
                                       TPM_BOOL decrypt)
{
    return (cached_stateblob.data != NULL) &&
           (cached_stateblob.blobtype == blobtype) &&
           (cached_stateblob.decrypt == decrypt);
}

/*
 * cached_stateblob_free: Free any previously loaded state blob
 */
static void cached_stateblob_free(void)
{
    free(cached_stateblob.data);
    cached_stateblob.data = NULL;
    cached_stateblob.data_length = 0;
}

/*
 * cached_stateblob_get_bloblength: get the total length of the cached blob
 */
static uint32_t cached_stateblob_get_bloblength(void)
{
    return cached_stateblob.data_length;
}

/*
 * cached_statblob_get: get stateblob data without copying them
 *
 * @offset: at which offset to get the data
 * @bufptr: pointer to a buffer pointer used to return buffer start
 * @length: pointer used to return number of available bytes in returned buffer
 */
static int cached_stateblob_get(uint32_t offset,
                                unsigned char **bufptr, size_t *length)
{
    if (cached_stateblob.data == NULL ||
        offset > cached_stateblob.data_length)
        return -1;

    *bufptr = &cached_stateblob.data[offset];
    *length = cached_stateblob.data_length - offset;

    return 0;
}

/*
 * cached_stateblob_load: load a state blob into the cache
 *
 * blobtype: the type of blob
 * decrypt: whether the blob is to be decrypted
 */
static TPM_RESULT cached_stateblob_load(uint32_t blobtype, TPM_BOOL decrypt)
{
    TPM_RESULT res = 0;
    const char *blobname = tpmlib_get_blobname(blobtype);
    uint32_t tpm_number = 0;

    if (!blobname)
        return TPM_BAD_PARAMETER;

    cached_stateblob_free();

    if (blobtype == PTM_BLOB_TYPE_VOLATILE)
        res = SWTPM_NVRAM_Store_Volatile();

    if (res == 0)
        res = SWTPM_NVRAM_GetStateBlob(&cached_stateblob.data,
                                       &cached_stateblob.data_length,
                                       tpm_number, blobname, decrypt,
                                       &cached_stateblob.is_encrypted);

    /* make sure the volatile state file is gone */
    if (blobtype == PTM_BLOB_TYPE_VOLATILE)
        SWTPM_NVRAM_DeleteName(tpm_number, blobname, FALSE);

    if (res == 0) {
        cached_stateblob.blobtype = blobtype;
        cached_stateblob.decrypt = decrypt;
    }

    if (blobtype == PTM_BLOB_TYPE_SAVESTATE)
        unlock_nvram(DEFAULT_LOCKING_RETRIES);

    return res;
}

/*
 * cached_state_blob_copy: copy the cached state blob to a destination buffer
 *
 * dest: destination buffer
 * destlen: size of the buffer
 * srcoffset: offset to copy from
 * copied: variable to return the number of copied bytes
 * is_encrypted: variable to return whether the blob is encrypted
 */
static int cached_stateblob_copy(void *dest, size_t destlen,
                                 uint32_t srcoffset, uint32_t *copied,
                                 TPM_BOOL *is_encrypted)
{
    int ret = -1;

    *copied = 0;

    if (cached_stateblob.data != NULL && cached_stateblob.data_length > 0) {

        if (srcoffset < cached_stateblob.data_length) {
            *copied = min(cached_stateblob.data_length - srcoffset, destlen);

            memcpy(dest, &cached_stateblob.data[srcoffset], *copied);

            *is_encrypted = cached_stateblob.is_encrypted;
        }

        ret = 0;
    }

    return ret;
}

/************************* worker thread ************************************/

/*
 * worker_thread: the worker thread
 */
static void worker_thread(gpointer data, gpointer user_data SWTPM_ATTR_UNUSED)
{
    struct thread_message *msg = (struct thread_message *)data;

    /* file_ops_lock not needed since thread_busy is set */

    switch (msg->type) {
    case MESSAGE_TPM_CMD:
        TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
                       ptm_request, ptm_req_len);
        ptm_read_offset = 0;
        break;
    case MESSAGE_IOCTL:
        break;
    }

    /* results are ready */
    worker_thread_mark_done();
}

/***************************** utility functions ****************************/

/*
 * tpm_start: Start the TPM
 *
 * Check whether the TPM's state directory exists and if it does
 * not exists, try to create it. Start the thread pool, initialize
 * libtpms and allocate a global TPM request buffer.
 *
 * @flags: libtpms init flags
 * @l_tpmversion: the version of the TPM
 * @res: the result from starting the TPM
 */
static int tpm_start(uint32_t flags, TPMLIB_TPMVersion l_tpmversion,
                     const char *json_profile, TPM_RESULT *res)
{
    DIR *dir;
    const char *uri = tpmstate_get_backend_uri();
    const char *tpmdir = uri + strlen("dir://");

    *res = TPM_FAIL;

    dir = opendir(tpmdir);
    if (dir) {
        closedir(dir);
    } else {
        if (mkdir(tpmdir, 0775)) {
            logprintf(STDERR_FILENO,
                      "Error: Could not open tpmstate dir %s\n",
                      tpmdir);
            return -1;
        }
    }

    pool = g_thread_pool_new(worker_thread,
                             NULL,
                             1,
                             FALSE,
                             NULL);
    if (!pool) {
        logprintf(STDERR_FILENO,
                  "Error: Could not create the thread pool.\n");
        return -1;
    }

    if(!ptm_request)
        ptm_request = malloc(4096);
    if(!ptm_request) {
        logprintf(STDERR_FILENO,
                  "Error: Could not allocate memory for request buffer.\n");
        goto error_del_pool;
    }

    g_storage_locked = !g_incoming_migration;

    *res = tpmlib_start(flags, l_tpmversion, g_storage_locked, json_profile);
    if (*res != TPM_SUCCESS)
        goto error_del_pool;

    logprintf(STDOUT_FILENO,
              "CUSE TPM successfully initialized.\n");

    return 0;

error_del_pool:
    g_thread_pool_free(pool, TRUE, TRUE);
    pool = NULL;

    return -1;
}

/*
 * ptm_write_fatal_error_response: Write fatal error response
 *
 * Write a fatal error response into the global ptm_response buffer.
 */
static void ptm_write_fatal_error_response(TPMLIB_TPMVersion l_tpmversion)
{
    tpmlib_write_fatal_error_response(&ptm_response,
                                      &ptm_res_len,
                                      &ptm_res_tot,
                                      l_tpmversion);
    ptm_read_offset = 0;
}

/*
 * ptm_send_startup: Send a TPM/TPM2_Startup
 */
static int ptm_send_startup(uint16_t startupType,
                            TPMLIB_TPMVersion l_tpmversion SWTPM_ATTR_UNUSED)
{
    uint32_t command_length;
    unsigned char command[sizeof(struct tpm_startup)];
    uint32_t max_command_length = sizeof(command);
    int ret = 0;
    TPM_RESULT rc = TPM_SUCCESS;

    command_length = tpmlib_create_startup_cmd(
                               startupType,
                               tpmversion,
                               command, max_command_length);
    if (command_length > 0) {
        g_lastCommand = tpmlib_get_cmd_ordinal(command, command_length);

        rc = TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
                           (unsigned char *)command, command_length);
        ptm_read_offset = 0;
    }

    if (rc || command_length == 0) {
        if (rc) {
            logprintf(STDERR_FILENO, "Could not send Startup: 0x%x\n", rc);
            ret = -1;
        }
    }

    return ret;
}

/************************************ read() support ***************************/

/*
 * ptm_read_result: Return the TPM response packet
 *
 * @req: the fuse_req_t
 * @size: the max. number of bytes to return to the requester
 */
static void ptm_read_result(fuse_req_t req, size_t size)
{
    size_t len = 0;

    /* prevent other threads from reading or writing cmds or doing ioctls */
    g_mutex_lock(FILE_OPS_LOCK);

    if (tpm_running) {
        /* wait until results are ready */
        worker_thread_wait_done();
    }

    if (ptm_read_offset < ptm_res_len) {
        len = ptm_res_len - ptm_read_offset;
        if (size < len)
            len = size;
    }

    fuse_reply_buf(req, (const char *)&ptm_response[ptm_read_offset], len);

    ptm_read_offset += len;

    g_mutex_unlock(FILE_OPS_LOCK);
}

/*
 * ptm_read_stateblob: get a TPM stateblob via the read() interface
 *
 * @req: the fuse_req_t
 * @size: the number of bytes to read
 *
 * The internal offset into the buffer is advanced by the number
 * of bytes that were copied. We switch back to command read/write
 * mode if an error occurred or once all bytes were read.
 */
static void ptm_read_stateblob(fuse_req_t req, size_t size)
{
    unsigned char *bufptr = NULL;
    size_t numbytes;
    size_t tocopy;

    if (cached_stateblob_get(tx_state.offset, &bufptr, &numbytes) < 0) {
        fuse_reply_err(req, EIO);
        tx_state.state = TX_STATE_RW_COMMAND;
    } else {
        tocopy = MIN(size, numbytes);
        tx_state.offset += tocopy;

        fuse_reply_buf(req, (char *)bufptr, tocopy);
        /* last transfer indicated by less bytes available than requested */
        if (numbytes < size) {
            tx_state.state = TX_STATE_RW_COMMAND;
        }
    }
}

/*
 * ptm_read: interface to POSIX read()
 *
 * @req: fuse_req_t
 * @size: number of bytes to read
 * @off: offset (not used)
 * @fi: fuse_file_info (not used)
 *
 * Depending on the current state of the transfer interface (read/write)
 * return either the results of TPM commands or a data of a TPM state blob.
 */
static void ptm_read(fuse_req_t req, size_t size, off_t off SWTPM_ATTR_UNUSED,
                     struct fuse_file_info *fi SWTPM_ATTR_UNUSED)
{
    switch (tx_state.state) {
    case TX_STATE_RW_COMMAND:
        ptm_read_result(req, size);
        break;
    case TX_STATE_SET_STATE_BLOB:
        fuse_reply_err(req, EIO);
        tx_state.state = TX_STATE_RW_COMMAND;
        break;
    case TX_STATE_GET_STATE_BLOB:
        ptm_read_stateblob(req, size);
        break;
    case TX_STATE_CLOSED:
        /* not possible */
        break;
    }
}

/*************************read/write stateblob support ***********************/

/*
 * ptm_set_stateblob_append: Append a piece of TPM state blob and transfer to TPM
 *
 * blobtype: the type of blob
 * data: the data to append
 * length: length of the data
 * is_encrypted: whether the blob is encrypted
 * is_last: whether this is the last part of the TPM state blob; if it is, the TPM
 *          state blob will then be transferred to the TPM
 */
static TPM_RESULT
ptm_set_stateblob_append(uint32_t blobtype,
                         const unsigned char *data, uint32_t length,
                         bool is_encrypted, bool is_last)
{
    TPM_RESULT res = 0;
    static struct stateblob stateblob;
    unsigned char *tmp;

    if (stateblob.type != blobtype) {
        /* new blob; clear old data */
        free(stateblob.data);
        stateblob.data = NULL;
        stateblob.length = 0;
        stateblob.type = blobtype;
        stateblob.is_encrypted = is_encrypted;

        /*
         * on the first call for a new state blob we allow 0 bytes to be written
         * this allows the user to transfer via write()
         */
        if (length == 0)
            return 0;
    }

    /* append */
    tmp = realloc(stateblob.data, stateblob.length + length);
    if (!tmp) {
        logprintf(STDERR_FILENO,
                  "Could not allocate %u bytes.\n", stateblob.length + length);
        /* error */
        free(stateblob.data);
        stateblob.data = NULL;
        stateblob.length = 0;
        stateblob.type = 0;

        return TPM_FAIL;
    } else
        stateblob.data = tmp;

    memcpy(&stateblob.data[stateblob.length], data, length);
    stateblob.length += length;

    if (!is_last) {
        /* full packet -- expecting more data */
        return res;
    }

    res = SWTPM_NVRAM_SetStateBlob(stateblob.data,
                                   stateblob.length,
                                   stateblob.is_encrypted,
                                   0 /* tpm_number */,
                                   blobtype);

    logprintf(STDERR_FILENO,
              "Deserialized state type %d (%s), length=%d, res=%d\n",
              blobtype, tpmlib_get_blobname(blobtype),
              stateblob.length, res);

    free(stateblob.data);
    stateblob.data = NULL;
    stateblob.length = 0;
    stateblob.type = 0;

    /* transfer of blob is complete */
    tx_state.state = TX_STATE_RW_COMMAND;

    return res;
}

/*
 * ptm_set_stateblob: set part of a TPM state blob
 *
 * @req: fuse_req_t
 * pss: ptm_setstate provided via ioctl()
 */
static void
ptm_set_stateblob(fuse_req_t req, ptm_setstate *pss)
{
    TPM_RESULT res = 0;
    TPM_BOOL is_encrypted =
        ((pss->u.req.state_flags & PTM_STATE_FLAG_ENCRYPTED) != 0);
    bool is_last = (sizeof(pss->u.req.data) != pss->u.req.length);

    if (pss->u.req.length > sizeof(pss->u.req.data)) {
        res = TPM_BAD_PARAMETER;
        goto send_response;
    }

    /* transfer of blob initiated */
    tx_state.state = TX_STATE_SET_STATE_BLOB;
    tx_state.blobtype = pss->u.req.type;
    tx_state.blob_is_encrypted = is_encrypted;
    tx_state.offset = 0;

    res = ptm_set_stateblob_append(pss->u.req.type,
                                   pss->u.req.data,
                                   pss->u.req.length,
                                   is_encrypted,
                                   is_last);

    if (res)
        tx_state.state = TX_STATE_RW_COMMAND;

 send_response:
    pss->u.resp.tpm_result = res;

    fuse_reply_ioctl(req, 0, pss, sizeof(*pss));
}

/*
 * ptm_get_stateblob_part: get part of a state blob
 *
 * @blobtype: the type of blob to get
 * @buffer: the buffer this function will write the blob into
 * @buffer_size: the size of the buffer
 * @offset: the offset into the state blob
 * @copied: pointer to int to indicate the number of bytes that were copied
 * @is_encryped: returns whether the state blob is encrypted
 */
static TPM_RESULT
ptm_get_stateblob_part(uint32_t blobtype,
                       unsigned char *buffer, size_t buffer_size,
                       uint32_t offset, uint32_t *copied,
                       TPM_BOOL decrypt, TPM_BOOL *is_encrypted)
{
    TPM_RESULT res = 0;

    if (!cached_stateblob_is_loaded(blobtype, decrypt)) {
        res = cached_stateblob_load(blobtype, decrypt);
    }

    if (res == 0) {
        cached_stateblob_copy(buffer, buffer_size,
                              offset, copied, is_encrypted);
    }

    return res;
}

/*
 * ptm_get_stateblob: Get the state blob from the TPM using ioctl()
 */
static void
ptm_get_stateblob(fuse_req_t req, ptm_getstate *pgs)
{
    TPM_RESULT res = 0;
    uint32_t blobtype = pgs->u.req.type;
    TPM_BOOL decrypt =
        ((pgs->u.req.state_flags & PTM_STATE_FLAG_DECRYPTED) != 0);
    TPM_BOOL is_encrypted = FALSE;
    uint32_t copied = 0;
    uint32_t offset = pgs->u.req.offset;
    uint32_t totlength;

    res = ptm_get_stateblob_part(blobtype,
                                 pgs->u.resp.data, sizeof(pgs->u.resp.data),
                                 pgs->u.req.offset, &copied,
                                 decrypt, &is_encrypted);

    totlength = cached_stateblob_get_bloblength();

    pgs->u.resp.state_flags = 0;
    if (is_encrypted)
        pgs->u.resp.state_flags |= PTM_STATE_FLAG_ENCRYPTED;

    pgs->u.resp.length = copied;
    pgs->u.resp.totlength = totlength;
    pgs->u.resp.tpm_result = res;
    logprintf(STDERR_FILENO,
              "Serialized state type %d, length=%d, totlength=%d, res=%d\n",
              blobtype, copied, totlength, res);

    if (res == 0) {
        if (offset + copied < totlength) {
            /* last byte was not copied */
            tx_state.state = TX_STATE_GET_STATE_BLOB;
            tx_state.blobtype = pgs->u.req.type;
            tx_state.blob_is_encrypted = is_encrypted;
            tx_state.offset = copied;
        } else {
            /* last byte was copied */
            tx_state.state = TX_STATE_RW_COMMAND;
        }
    } else {
        /* error occurred */
        tx_state.state = TX_STATE_RW_COMMAND;
    }

    fuse_reply_ioctl(req, 0, pgs, sizeof(pgs->u.resp));
}

/*********************************** write() support *************************/

/*
 * ptm_write_stateblob: Write the state blob using the write() interface
 *
 * @req: the fuse_req_t
 * @buf: the buffer with the data
 * @size: the number of bytes in the buffer
 *
 * The data are appended to an existing buffer that was created with the
 * initial ioctl().
 */
static void ptm_write_stateblob(fuse_req_t req, const char *buf, size_t size)
{
    TPM_RESULT res;

    res = ptm_set_stateblob_append(tx_state.blobtype,
                                   (unsigned char *)buf, size,
                                   tx_state.blob_is_encrypted,
                                   (size == 0));
    if (res) {
        tx_state.state = TX_STATE_RW_COMMAND;
        fuse_reply_err(req, EIO);
    } else {
        fuse_reply_write(req, size);
    }
}

/*
 * ptm_write_cmd: User writing a TPM command
 *
 * req: fuse_req_t
 * buf: the buffer containing the TPM command
 * size: the size of the buffer
 * tpmversion: the version of the TPM
 */
static void ptm_write_cmd(fuse_req_t req, const char *buf, size_t size,
                          TPMLIB_TPMVersion l_tpmversion)
{
    uint32_t lastCommand;

    ptm_req_len = size;
    ptm_res_len = 0;

    /* prevent other threads from reading or writing cmds or doing ioctls */
    g_mutex_lock(FILE_OPS_LOCK);

    if (tpm_running) {
        /* before processing a command ensure that the NVRAM is locked */
        if (!ensure_locked_storage()) {
            fuse_reply_err(req, EIO);
            goto cleanup;
        }

        /* ensure that we only ever work on one TPM command */
        if (worker_thread_is_busy()) {
            fuse_reply_err(req, EBUSY);
            goto cleanup;
        }

        if (ptm_req_len > TPM_REQ_MAX)
            ptm_req_len = TPM_REQ_MAX;

        /* process SetLocality command, if */
        tpmlib_process(&ptm_response, &ptm_res_len, &ptm_res_tot,
                       (unsigned char *)buf, ptm_req_len,
                       locality_flags, &locality, tpmversion);
        if (ptm_res_len) {
            ptm_read_offset = 0;
            goto skip_process;
        }

        lastCommand = tpmlib_get_cmd_ordinal((unsigned char *)buf, ptm_req_len);
        if (lastCommand != TPM_ORDINAL_NONE)
            g_lastCommand = lastCommand;

        if (tpmlib_is_request_cancelable(l_tpmversion,
                                         (const unsigned char*)buf,
                                         ptm_req_len)) {
            /* have command processed by thread pool */
            memcpy(ptm_request, buf, ptm_req_len);

            g_msg.type = MESSAGE_TPM_CMD;

            worker_thread_mark_busy();

            g_thread_pool_push(pool, &g_msg, NULL);
        } else {
            /* direct processing */
            TPMLIB_Process(&ptm_response, &ptm_res_len, &ptm_res_tot,
                           (unsigned char *)buf, ptm_req_len);
            ptm_read_offset = 0;
        }
    } else {
        /* TPM not initialized; return error */
        ptm_write_fatal_error_response(l_tpmversion);
    }

skip_process:
    fuse_reply_write(req, ptm_req_len);

cleanup:
    g_mutex_unlock(FILE_OPS_LOCK);

    return;
}

/*
 * ptm_write: low-level write() interface; calls appropriate function depending
 *            on what is being transferred using the write()
 */
static void ptm_write(fuse_req_t req, const char *buf, size_t size,
                      off_t off SWTPM_ATTR_UNUSED,
                      struct fuse_file_info *fi SWTPM_ATTR_UNUSED)
{
    switch (tx_state.state) {
    case TX_STATE_RW_COMMAND:
        ptm_write_cmd(req, buf, size, tpmversion);
        break;
    case TX_STATE_GET_STATE_BLOB:
        fuse_reply_err(req, EIO);
        tx_state.state = TX_STATE_RW_COMMAND;
        break;
    case TX_STATE_SET_STATE_BLOB:
        ptm_write_stateblob(req, buf, size);
        break;
    case TX_STATE_CLOSED:
        /* not possible */
        break;
    }
}

/*
 * ptm_open: interface to POSIX open()
 */
static void ptm_open(fuse_req_t req, struct fuse_file_info *fi)
{
    if (tx_state.state != TX_STATE_CLOSED) {
        fuse_reply_err(req, EBUSY);
        return;
    }

    tx_state.state = TX_STATE_RW_COMMAND;

    fuse_reply_open(req, fi);
}

/*
 * ptm_release:
 */
static void ptm_release(fuse_req_t req,
                        struct fuse_file_info *fi SWTPM_ATTR_UNUSED)
{
    tx_state.state = TX_STATE_CLOSED;

    fuse_reply_err(req, 0);
}

/*
 * ptm_ioctl : ioctl execution
 *
 * req: the fuse_req_t used to send response with
 * cmd: the ioctl request code
 * arg: the pointer the application used for calling the ioctl (3rd param)
 * fi:
 * flags: some flags provided by fuse
 * in_buf: the copy of the input buffer
 * in_bufsz: size of the input buffer; provided by fuse and has size of
 *           needed buffer
 * out_bufsz: size of the output buffer; provided by fuse and has size of
 *            needed buffer
 */
static void ptm_ioctl(fuse_req_t req, int cmd, void *arg,
                      struct fuse_file_info *fi SWTPM_ATTR_UNUSED,
                      unsigned flags SWTPM_ATTR_UNUSED,
                      const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
    TPM_RESULT res = TPM_FAIL;
    bool exit_prg = FALSE;
    ptm_init *init_p;
    TPM_MODIFIER_INDICATOR orig_locality;

    /* prevent other threads from writing or doing ioctls */
    g_mutex_lock(FILE_OPS_LOCK);

    /* some commands have to wait until the worker thread is done */
    switch(cmd) {
    case PTM_GET_CAPABILITY:
    case PTM_SET_LOCALITY:
    case PTM_CANCEL_TPM_CMD:
    case PTM_GET_CONFIG:
    case PTM_SET_BUFFERSIZE:
    case PTM_LOCK_STORAGE:
        /* no need to wait */
        break;
    case PTM_INIT:
    case PTM_SHUTDOWN:
    case PTM_GET_TPMESTABLISHED:
    case PTM_RESET_TPMESTABLISHED:
    case PTM_HASH_START:
    case PTM_HASH_DATA:
    case PTM_HASH_END:
    case PTM_STORE_VOLATILE:
    case PTM_GET_STATEBLOB:
    case PTM_SET_STATEBLOB:
        if (tpm_running)
            worker_thread_wait_done();
        break;
    }

    switch (cmd) {
    case PTM_GET_CAPABILITY:
        if (out_bufsz != sizeof(ptm_cap)) {
            struct iovec iov = { arg, sizeof(uint8_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_cap ptm_caps;
            switch (tpmversion) {
            case TPMLIB_TPM_VERSION_2:
                ptm_caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN
                    | PTM_CAP_GET_TPMESTABLISHED
                    | PTM_CAP_SET_LOCALITY
                    | PTM_CAP_HASHING
                    | PTM_CAP_CANCEL_TPM_CMD
                    | PTM_CAP_STORE_VOLATILE
                    | PTM_CAP_RESET_TPMESTABLISHED
                    | PTM_CAP_GET_STATEBLOB
                    | PTM_CAP_SET_STATEBLOB
                    | PTM_CAP_STOP
                    | PTM_CAP_GET_CONFIG
                    | PTM_CAP_SET_BUFFERSIZE
                    | PTM_CAP_GET_INFO
                    | PTM_CAP_LOCK_STORAGE;
                break;
            case TPMLIB_TPM_VERSION_1_2:
                ptm_caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN
                    | PTM_CAP_GET_TPMESTABLISHED
                    | PTM_CAP_SET_LOCALITY
                    | PTM_CAP_HASHING
                    | PTM_CAP_CANCEL_TPM_CMD
                    | PTM_CAP_STORE_VOLATILE
                    | PTM_CAP_RESET_TPMESTABLISHED
                    | PTM_CAP_GET_STATEBLOB
                    | PTM_CAP_SET_STATEBLOB
                    | PTM_CAP_STOP
                    | PTM_CAP_GET_CONFIG
                    | PTM_CAP_SET_BUFFERSIZE
                    | PTM_CAP_GET_INFO
                    | PTM_CAP_LOCK_STORAGE;
                break;
            }
            fuse_reply_ioctl(req, 0, &ptm_caps, sizeof(ptm_caps));
        }
        break;

    case PTM_INIT:
        if (in_bufsz != sizeof(ptm_init)) {
            struct iovec iov = { arg, sizeof(uint8_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            init_p = (ptm_init *)in_buf;

            worker_thread_end();

            if (tpm_running && !g_disable_auto_shutdown)
                tpmlib_maybe_send_tpm2_shutdown(tpmversion,
                                                &g_lastCommand);

            TPMLIB_Terminate();

            tpm_running = false;
            if (tpm_start(init_p->u.req.init_flags, tpmversion,
                          g_json_profile, &res) < 0) {
                logprintf(STDERR_FILENO,
                          "Error: Could not initialize the TPM.\n");
            } else {
                tpm_running = true;
                SWTPM_G_FREE(g_json_profile);
            }
            init_p->u.resp.tpm_result = res;
            fuse_reply_ioctl(req, 0, init_p, sizeof(*init_p));
        }
        break;

    case PTM_STOP:
        worker_thread_end();

        if (tpm_running && !g_disable_auto_shutdown)
            tpmlib_maybe_send_tpm2_shutdown(tpmversion, &g_lastCommand);

        res = TPM_SUCCESS;
        TPMLIB_Terminate();

        tpm_running = false;

        free(ptm_response);
        ptm_response = NULL;

        fuse_reply_ioctl(req, 0, &res, sizeof(res));

        break;

    case PTM_SHUTDOWN:
        worker_thread_end();

        if (tpm_running && !g_disable_auto_shutdown)
            tpmlib_maybe_send_tpm2_shutdown(tpmversion, &g_lastCommand);

        res = TPM_SUCCESS;
        TPMLIB_Terminate();

        tpm_running = false;

        free(ptm_response);
        ptm_response = NULL;

        fuse_reply_ioctl(req, 0, &res, sizeof(res));
        exit_prg = TRUE;

        break;

    case PTM_GET_TPMESTABLISHED:
        if (!tpm_running)
            goto error_not_running;

        if (out_bufsz != sizeof(ptm_est)) {
            struct iovec iov = { arg, sizeof(uint8_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_est te;
            memset(&te, 0, sizeof(te));
            te.u.resp.tpm_result = TPM_IO_TpmEstablished_Get(&te.u.resp.bit);
            fuse_reply_ioctl(req, 0, &te, sizeof(te));
        }
        break;

    case PTM_RESET_TPMESTABLISHED:
        if (!tpm_running)
            goto error_not_running;

        if (in_bufsz != sizeof(ptm_reset_est)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_reset_est *re = (ptm_reset_est *)in_buf;
            if (re->u.req.loc > 4) {
                res = TPM_BAD_LOCALITY;
            } else {
                /* set locality and reset flag in one command */
                orig_locality = locality;
                locality = re->u.req.loc;

                res = TPM_IO_TpmEstablished_Reset();

                locality = orig_locality;
                fuse_reply_ioctl(req, 0, &res, sizeof(res));
            }
        }
        break;

    case PTM_SET_LOCALITY:
        if (in_bufsz != sizeof(ptm_loc)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_loc *l = (ptm_loc *)in_buf;
            if (l->u.req.loc > 4 ||
                (l->u.req.loc == 4 &&
                 locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) {
                res = TPM_BAD_LOCALITY;
            } else {
                res = TPM_SUCCESS;
                locality = l->u.req.loc;
            }
            l->u.resp.tpm_result = res;
            fuse_reply_ioctl(req, 0, l, sizeof(*l));
        }
        break;

    case PTM_HASH_START:
        if (!tpm_running)
            goto error_not_running;

        res = TPM_IO_Hash_Start();
        fuse_reply_ioctl(req, 0, &res, sizeof(res));
        break;

    case PTM_HASH_DATA:
        if (!tpm_running)
            goto error_not_running;

        if (in_bufsz != sizeof(ptm_hdata)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_hdata *data = (ptm_hdata *)in_buf;
            if (data->u.req.length <= sizeof(data->u.req.data)) {
                res = TPM_IO_Hash_Data(data->u.req.data,
                                       data->u.req.length);
            } else {
                res = TPM_FAIL;
            }
            data->u.resp.tpm_result = res;
            fuse_reply_ioctl(req, 0, data, sizeof(*data));
        }
        break;

    case PTM_HASH_END:
        if (!tpm_running)
            goto error_not_running;

        res = TPM_IO_Hash_End();
        fuse_reply_ioctl(req, 0, &res, sizeof(res));
        break;

    case PTM_CANCEL_TPM_CMD:
        if (!tpm_running)
            goto error_not_running;

        /* for cancellation to work, the TPM would have to
         * execute in another thread that polls on a cancel
         * flag
         */
        res = TPMLIB_CancelCommand();
        fuse_reply_ioctl(req, 0, &res, sizeof(res));
        break;

    case PTM_STORE_VOLATILE:
        if (!tpm_running)
            goto error_not_running;

        res = SWTPM_NVRAM_Store_Volatile();
        fuse_reply_ioctl(req, 0, &res, sizeof(res));

        cached_stateblob_free();
        break;

    case PTM_GET_STATEBLOB:
        if (!tpm_running)
            goto error_not_running;

        if (in_bufsz != sizeof(ptm_getstate)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_get_stateblob(req, (ptm_getstate *)in_buf);
        }
        break;

    case PTM_SET_STATEBLOB:
        if (tpm_running)
            goto error_running;

        /* tpm state dir must be set */
        SWTPM_NVRAM_Init();
        if ((exit_prg = !ensure_locked_storage())) {
            fuse_reply_err(req, EIO);
        } else if (in_bufsz != sizeof(ptm_setstate)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_set_stateblob(req, (ptm_setstate *)in_buf);
        }
        break;

    case PTM_GET_CONFIG:
        if (out_bufsz != sizeof(ptm_getconfig)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_getconfig pgs;
            pgs.u.resp.tpm_result = 0;
            pgs.u.resp.flags = 0;
            if (SWTPM_NVRAM_Has_FileKey())
                pgs.u.resp.flags |= PTM_CONFIG_FLAG_FILE_KEY;
            if (SWTPM_NVRAM_Has_MigrationKey())
                pgs.u.resp.flags |= PTM_CONFIG_FLAG_MIGRATION_KEY;
            fuse_reply_ioctl(req, 0, &pgs, sizeof(pgs));
        }
        break;

    case PTM_SET_BUFFERSIZE:
        if (out_bufsz != sizeof(ptm_setbuffersize)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_setbuffersize *in_psbs = (ptm_setbuffersize *)in_buf;
            ptm_setbuffersize out_psbs;
            uint32_t buffersize, minsize, maxsize;

            buffersize = in_psbs->u.req.buffersize;

            if (buffersize > 0 && tpm_running)
                goto error_running;

            buffersize = TPMLIB_SetBufferSize(buffersize,
                                              &minsize,
                                              &maxsize);

            out_psbs.u.resp.tpm_result = TPM_SUCCESS;
            out_psbs.u.resp.buffersize = buffersize;
            out_psbs.u.resp.minsize = minsize;
            out_psbs.u.resp.maxsize = maxsize;
            fuse_reply_ioctl(req, 0, &out_psbs, sizeof(out_psbs));
        }
        break;

    case PTM_GET_INFO:
        if (out_bufsz != sizeof(ptm_getinfo)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_getinfo *in_pgi = (ptm_getinfo *)in_buf;
            ptm_getinfo out_pgi;
            char *info_data;
            uint32_t length, offset;

            info_data = TPMLIB_GetInfo(in_pgi->u.req.flags);
            if (!info_data)
                goto error_memory;

            offset = in_pgi->u.req.offset;
            if (offset >= strlen(info_data)) {
                free(info_data);
                goto error_bad_input;
            }

            length = min(strlen(info_data) + 1 - offset,
                         sizeof(out_pgi.u.resp.buffer));

            out_pgi.u.resp.tpm_result = 0;
            out_pgi.u.resp.totlength = strlen(info_data) + 1;
            out_pgi.u.resp.length = length;
            /* client has to collect whole string in case buffer is too small */
            memcpy(out_pgi.u.resp.buffer, &info_data[offset], length);
            free(info_data);

            fuse_reply_ioctl(req, 0, &out_pgi, sizeof(out_pgi));
        }
        break;

    case PTM_LOCK_STORAGE:
        if (out_bufsz != sizeof(ptm_lockstorage)) {
            struct iovec iov = { arg, sizeof(uint32_t) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            ptm_lockstorage *in_pls = (ptm_lockstorage *)in_buf;
            ptm_lockstorage out_pls;

            g_locking_retries = in_pls->u.req.retries;

            if (!ensure_locked_storage())
                out_pls.u.resp.tpm_result = TPM_FAIL;
            else
                out_pls.u.resp.tpm_result = TPM_SUCCESS;
            fuse_reply_ioctl(req, 0, &out_pls, sizeof(out_pls));
        }

        break;

    default:
        fuse_reply_err(req, EINVAL);
    }

cleanup:
    g_mutex_unlock(FILE_OPS_LOCK);

    if (exit_prg) {
        logprintf(STDOUT_FILENO, "CUSE TPM is shutting down.\n");
        ptm_cleanup();
        fuse_session_exit(ptm_fuse_session);
    }

    return;

error_bad_input:
    res = TPM_BAD_PARAMETER;
    fuse_reply_ioctl(req, 0, &res, sizeof(res));

    goto cleanup;

error_running:
error_not_running:
    res = TPM_BAD_ORDINAL;
    fuse_reply_ioctl(req, 0, &res, sizeof(res));

    goto cleanup;

error_memory:
    res = TPM_SIZE;
    fuse_reply_ioctl(req, 0, &res, sizeof(res));

    goto cleanup;
}

static void ptm_init_done(void *userdata)
{
    struct cuse_param *param = userdata;
    int ret;

    /* at this point the entry in /dev/ is available */
    if (pidfile_write(getpid()) < 0) {
        ret = -13;
        goto error_exit;
    }

    if (param->runas) {
        ret = change_process_owner(param->runas);
        if (ret)
            goto error_exit;
    }

    if (create_seccomp_profile(true, param->seccomp_action) < 0) {
        ret = -14;
        goto error_exit;
    }

    return;

error_exit:
    ptm_cleanup();

    exit(ret);
}

static void ptm_cleanup(void)
{
    pidfile_remove();
    log_global_free();
    tpmstate_global_free();
    SWTPM_NVRAM_Shutdown();
}

static const struct cuse_lowlevel_ops clops = {
    .open = ptm_open,
    .read = ptm_read,
    .write = ptm_write,
    .release = ptm_release,
    .ioctl = ptm_ioctl,
    .init_done = ptm_init_done,
};

/* ptm_cuse_lowlevel_main is like cuse_lowlevel_main with the difference that
 * it uses a global ptm_fuse_session so we can call fuse_session_exit() on it
 * for a graceful exit with cleanups.
 */
static int
ptm_cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
                       const struct cuse_lowlevel_ops *clop, void *userdata)
{
    int mt;
    int ret;
    struct cuse_param *param = userdata;

    ptm_fuse_session = cuse_lowlevel_setup(argc, argv, ci, clop, &mt,
                                           userdata);
    if (ptm_fuse_session == NULL)
        return 1;

    if (param->seccomp_action == SWTPM_SECCOMP_ACTION_NONE && mt)
        ret = fuse_session_loop_mt(ptm_fuse_session);
    else
        ret = fuse_session_loop(ptm_fuse_session);

    cuse_lowlevel_teardown(ptm_fuse_session);
    if (ret < 0)
        ret = 1;

    return ret;
}

#ifndef HAVE_SWTPM_CUSE_MAIN
int main(int argc, char **argv)
{
    const char *prgname = argv[0];
    const char *iface = "";
#else
int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *iface)
{
#endif
    int opt, longindex = 0;
    enum TPMLIB_InfoFlags infoflags = 0;
    static struct option longopts[] = {
        {"maj"           , required_argument, 0, 'M'},
        {"min"           , required_argument, 0, 'm'},
        {"name"          , required_argument, 0, 'n'},
        {"runas"         , required_argument, 0, 'r'},
        {"chroot"        , required_argument, 0, 'R'},
        {"log"           , required_argument, 0, 'l'},
        {"locality"      , required_argument, 0, 'L'},
        {"key"           , required_argument, 0, 'k'},
        {"migration-key" , required_argument, 0, 'K'},
        {"pid"           , required_argument, 0, 'p'},
        {"tpmstate"      , required_argument, 0, 's'},
        {"flags"         , required_argument, 0, 'F'},
        {"tpm2"          ,       no_argument, 0, '2'},
        {"help"          ,       no_argument, 0, 'h'},
        {"version"       ,       no_argument, 0, 'v'},
#ifdef WITH_SECCOMP
        {"seccomp"       , required_argument, 0, 'S'},
#endif
        {"migration"     , required_argument, 0, 'i'},
        {"print-capabilities"
                         ,       no_argument, 0, 'a'},
        {"print-states"  ,       no_argument, 0, 'e'},
        {"profile"       , required_argument, 0, 'I'},
        {"print-profiles",       no_argument, 0, 'N'},
        {"print-info"    , required_argument, 0, 'x'},
        {NULL            , 0                , 0, 0  },
    };
    struct cuse_info cinfo;
    struct cuse_param param = {
        .startupType = _TPM_ST_NONE,
    };
    g_autofree gchar *jsoninfo = NULL;
    const char *devname = NULL;
    char *cinfo_argv[1] = { 0 };
    unsigned int num;
    const char *uri = NULL;
    char *end_ptr = NULL;
    int n, tpmfd;
    char path[PATH_MAX];
    int ret = 0;
    bool printcapabilities = false;
    bool printstates = false;
    bool printprofiles = false;
    bool need_init_cmd = true;
    TPM_RESULT res;

    memset(&cinfo, 0, sizeof(cinfo));
    memset(&param, 0, sizeof(param));
#if GLIB_MINOR_VERSION >= 32
    g_mutex_init(FILE_OPS_LOCK);
#else
    FILE_OPS_LOCK = g_mutex_new();
#endif

    log_set_prefix("swtpm: ");

    tpmversion = TPMLIB_TPM_VERSION_1_2;

    while (true) {
        opt = getopt_long(argc, argv, "M:m:n:r:R:hv", longopts, &longindex);

        if (opt == -1)
            break;

        switch (opt) {
        case 'M': /* major */
            if (sscanf(optarg, "%u", &num) != 1) {
                logprintf(STDERR_FILENO, "Could not parse major number\n");
                ret = -1;
                goto exit;
            }
            if (num > 65535) {
                logprintf(STDERR_FILENO,
                          "Major number outside valid range [0 - 65535]\n");
                ret = -1;
                goto exit;
            }
            cinfo.dev_major = num;
            break;
        case 'm': /* minor */
            if (sscanf(optarg, "%u", &num) != 1) {
                logprintf(STDERR_FILENO, "Could not parse major number\n");
                ret = -1;
                goto exit;
            }
            if (num > 65535) {
                logprintf(STDERR_FILENO,
                          "Major number outside valid range [0 - 65535]\n");
                ret = -1;
                goto exit;
            }
            cinfo.dev_minor = num;
            break;
        case 'n': /* name */
            if (!cinfo.dev_info_argc) {
                cinfo_argv[0] = calloc(1, strlen("DEVNAME=") + strlen(optarg) + 1);
                if (!cinfo_argv[0]) {
                    logprintf(STDERR_FILENO, "Out of memory\n");
                    ret = -1;
                    goto exit;
                }
                devname = optarg;

                strcpy(cinfo_argv[0], "DEVNAME=");
                strcat(cinfo_argv[0], optarg);

                cinfo.dev_info_argc = 1;
                cinfo.dev_info_argv = (const char **)cinfo_argv;
            }
            break;
        case 'r': /* runas */
            param.runas = optarg;
            break;
        case 'R':
            param.chroot = optarg;
            break;
        case 'l': /* log */
            param.logging = optarg;
            break;
        case 'k': /* key */
            param.keydata = optarg;
            break;
        case 'K': /* migration-key */
            param.migkeydata = optarg;
            break;
        case 'p': /* pid */
            param.piddata = optarg;
            break;
        case 's': /* tpmstate */
            param.tpmstatedata = optarg;
            break;
        case 'L':
            param.localitydata = optarg;
            break;
        case 'F':
            param.flagsdata = optarg;
            break;
        case '2':
            tpmversion = TPMLIB_TPM_VERSION_2;
            break;
        case 'S':
            param.seccompdata = optarg;
            break;
        case 'i': /* --migration */
            param.migrationdata = optarg;
            break;
        case 'I': /* --profile */
            param.profiledata = optarg;
            break;
        case 'h': /* help */
            usage(stdout, prgname, iface);
            goto exit;
        case 'a':
            printcapabilities = true;
            break;
        case 'e':
            printstates = true;
            break;
        case 'N': /* --print-profiles */
            printprofiles = true;
            break;
        case 'x': /* --print-info */
            errno = 0;
            infoflags = strtoul(optarg, &end_ptr, 0);
            if (infoflags != (unsigned int)infoflags || errno || end_ptr[0] != '\0') {
                logprintf(STDERR_FILENO,
                          "Cannot parse info value '%s'.\n", optarg);
                ret = -1;
                goto exit;
            }
            break;
        case 'v': /* version */
            fprintf(stdout, "TPM emulator CUSE interface version %d.%d.%d, "
                    "Copyright (c) 2014-2015 IBM Corp.\n",
                    SWTPM_VER_MAJOR,
                    SWTPM_VER_MINOR,
                    SWTPM_VER_MICRO);
            goto exit;
        }
    }

    if (optind < argc) {
        logprintf(STDERR_FILENO,
                  "Unknown parameter '%s'\n", argv[optind]);
        ret = EXIT_FAILURE;
        goto exit;
    }

    if (setuid(0)) {
        logprintf(STDERR_FILENO, "Error: Unable to setuid root. uid = %d, "
                  "euid = %d, gid = %d\n", getuid(), geteuid(), getgid());
        ret = -4;
        goto exit;
    }

    if (param.chroot) {
        if (do_chroot(param.chroot) < 0) {
            ret = EXIT_FAILURE;
            goto exit;
        }
    }

    if (param.runas) {
        if (!getpwnam(param.runas)) {
            logprintf(STDERR_FILENO, "User '%s' does not exist\n",
                      param.runas);
            ret = -5;
            goto exit;
        }
    }

    if (handle_log_options(param.logging) < 0) {
        ret = EXIT_FAILURE;
        goto exit;
    }

    if (printcapabilities) {
        /*
         * Choose the TPM version so that getting/setting buffer size works.
         * Ignore failure, for backward compatibility when TPM 1.2 is disabled.
         */
        ret = capabilities_print_json(true, tpmversion) ? EXIT_FAILURE : EXIT_SUCCESS;
        goto exit;
    }

    if (tpmlib_choose_tpm_version(tpmversion) != TPM_SUCCESS) {
        ret = EXIT_FAILURE;
        goto exit;
    }

    tpmstate_set_version(tpmversion);

    if (printstates) {
        if (handle_tpmstate_options(param.tpmstatedata) < 0) {
            ret = EXIT_FAILURE;
            goto exit;
        }
        if (param.tpmstatedata == NULL) {
            logprintf(STDERR_FILENO,
                      "Error: --tpmstate option is required for --print-states\n");
            ret = EXIT_FAILURE;
            goto exit;
        }
        ret = SWTPM_NVRAM_PrintJson();
        ret = ret ? EXIT_FAILURE : EXIT_SUCCESS;
        goto exit;
    }

    if (printprofiles) {
        print_profiles();
        goto exit;
    }

    if (!cinfo.dev_info_argv) {
        logprintf(STDERR_FILENO, "Error: device name missing\n");
        ret = -2;
        goto exit;
    }

    if (handle_key_options(param.keydata) < 0 ||
        handle_migration_key_options(param.migkeydata) < 0 ||
        handle_pid_options(param.piddata) < 0 ||
        handle_tpmstate_options(param.tpmstatedata) < 0 ||
        handle_seccomp_options(param.seccompdata, &param.seccomp_action) < 0 ||
        handle_locality_options(param.localitydata, &locality_flags) < 0 ||
        handle_flags_options(param.flagsdata, &need_init_cmd,
                             &param.startupType, &g_disable_auto_shutdown) < 0 ||
        handle_migration_options(param.migrationdata, &g_incoming_migration,
                                 &g_release_lock_outgoing) < 0 ||
        handle_profile_options(param.profiledata, &g_json_profile) < 0) {
        ret = -3;
        goto exit;
    }

    uri = tpmstate_get_backend_uri();
    if (uri == NULL) {
        logprintf(STDERR_FILENO,
                  "Error: No TPM state directory is defined; "
                  "TPM_PATH is not set\n");
        ret = -1;
        goto exit;
    }

    n = snprintf(path, sizeof(path), "/dev/%s", devname);
    if (n < 0) {
        logprintf(STDERR_FILENO,
                  "Error: Could not create device file name\n");
        ret = -1;
        goto exit;
    }
    if (n >= (int)sizeof(path)) {
        logprintf(STDERR_FILENO,
                  "Error: Buffer too small to create device file name\n");
        ret = -1;
        goto exit;
    }

    tpmfd = open(path, O_RDWR);
    if (tpmfd >= 0) {
        close(tpmfd);
        logprintf(STDERR_FILENO,
                  "Error: A device '%s' already exists.\n",
                  path);
        ret = -1;
        goto exit;
    }

    if (tpmlib_register_callbacks(&cbs) != TPM_SUCCESS) {
        ret = -1;
        goto exit;
    }

    worker_thread_init();

    g_mutex_lock(FILE_OPS_LOCK);

    if (!need_init_cmd || (infoflags && tpmstate_get_backend_uri())) {
        if (tpm_start(0, tpmversion, g_json_profile, &res) < 0) {
            ret = -1;
            goto err_unlock;
        }
        tpm_running = true;
        SWTPM_G_FREE(g_json_profile);
    }

    if (infoflags) {
        /* returns more information with tpmstate active */
        jsoninfo = TPMLIB_GetInfo(infoflags);
        printf("%s\n", jsoninfo);
        goto err_unlock;
    }

    if (param.startupType != _TPM_ST_NONE) {
        if (ptm_send_startup(param.startupType, tpmversion) < 0) {
            ret = -1;
            goto err_unlock;
        }
    }

    g_mutex_unlock(FILE_OPS_LOCK);

    ret = ptm_cuse_lowlevel_main(1, argv, &cinfo, &clops, &param);

exit:
    free(g_json_profile);
    ptm_cleanup();
    free(cinfo_argv[0]);
#if GLIB_MINOR_VERSION < 32
    g_mutex_free(FILE_OPS_LOCK);
#endif

    return ret;

err_unlock:
    g_mutex_unlock(FILE_OPS_LOCK);

    goto exit;
}
