/*
 * Copyright (c) 2019, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "credstore"

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

#include <android-base/logging.h>
#include <android-base/stringprintf.h>

#include <android/security/identity/ICredentialStore.h>

#include "Util.h"

namespace android {
namespace security {
namespace identity {

using ::android::base::StringPrintf;

Status halStatusToError(const Status& halStatus, int credStoreError) {
    string message = StringPrintf(
        "HAL failed with exception code %d (%s), service-specific error code %d, message '%s'",
        halStatus.exceptionCode(), Status::exceptionToString(halStatus.exceptionCode()).c_str(),
        halStatus.serviceSpecificErrorCode(), halStatus.exceptionMessage().c_str());
    return Status::fromServiceSpecificError(credStoreError, message.c_str());
}

Status halStatusToGenericError(const Status& halStatus) {
    return halStatusToError(halStatus, ICredentialStore::ERROR_GENERIC);
}

optional<vector<uint8_t>> fileGetContents(const string& path) {
    int fd = open(path.c_str(), O_RDONLY);
    if (fd == -1) {
        PLOG(ERROR) << "Error opening " << path;
        return {};
    }

    struct stat statbuf;
    if (fstat(fd, &statbuf) != 0) {
        PLOG(ERROR) << "Error statting " << path;
        close(fd);
        return {};
    }
    vector<uint8_t> data;
    data.resize(statbuf.st_size);

    uint8_t* p = data.data();
    size_t remaining = data.size();
    while (remaining > 0) {
        ssize_t numRead = TEMP_FAILURE_RETRY(read(fd, p, remaining));
        if (numRead <= 0) {
            PLOG(ERROR) << "Failed reading from '" << path << "'";
            close(fd);
            return {};
        }
        p += numRead;
        remaining -= numRead;
    }
    close(fd);

    return data;
}

bool fileSetContents(const string& path, const vector<uint8_t>& data) {
    char tempName[4096];
    int fd;

    string tempNameStr = path + ".XXXXXX";
    if (tempNameStr.size() >= sizeof tempName - 1) {
        LOG(ERROR) << "Path name too long";
        return false;
    }
    strncpy(tempName, tempNameStr.c_str(), sizeof tempName);

    fd = mkstemp(tempName);
    if (fd == -1) {
        PLOG(ERROR) << "Error creating temp file for '" << path << "'";
        return false;
    }

    const uint8_t* p = data.data();
    size_t remaining = data.size();
    while (remaining > 0) {
        ssize_t numWritten = TEMP_FAILURE_RETRY(write(fd, p, remaining));
        if (numWritten <= 0) {
            PLOG(ERROR) << "Failed writing into temp file for '" << path << "'";
            close(fd);
            return false;
        }
        p += numWritten;
        remaining -= numWritten;
    }

    if (TEMP_FAILURE_RETRY(fsync(fd))) {
        PLOG(ERROR) << "Failed fsyncing temp file for '" << path << "'";
        close(fd);
        return false;
    }
    close(fd);

    if (rename(tempName, path.c_str()) != 0) {
        PLOG(ERROR) << "Error renaming temp file for '" << path << "'";
        close(fd);
        return false;
    }

    return true;
}

}  // namespace identity
}  // namespace security
}  // namespace android