/*
 * Copyright (C) 2015-2022 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
 */

#include "config.h"
#include "NetworkCacheBlobStorage.h"

#include "Logging.h"
#include "NetworkCacheFileSystem.h"
#include <fcntl.h>
#include <wtf/FileSystem.h>
#include <wtf/RunLoop.h>
#include <wtf/SHA1.h>

#if !OS(WINDOWS)
#include <sys/stat.h>
#include <unistd.h>
#endif

namespace WebKit {
namespace NetworkCache {

BlobStorage::BlobStorage(const String& blobDirectoryPath, Salt salt)
    : m_blobDirectoryPath(blobDirectoryPath)
    , m_salt(salt)
{
}

String BlobStorage::blobDirectoryPathIsolatedCopy() const
{
    return m_blobDirectoryPath.isolatedCopy();
}

void BlobStorage::synchronize()
{
    ASSERT(!RunLoop::isMain());

    auto blobDirectoryPath = blobDirectoryPathIsolatedCopy();
    FileSystem::makeAllDirectories(blobDirectoryPath);

    m_approximateSize = 0;
    auto blobDirectory = blobDirectoryPath;
    traverseDirectory(blobDirectory, [this, &blobDirectory](const String& name, DirectoryEntryType type) {
        if (type != DirectoryEntryType::File)
            return;
        auto path = FileSystem::pathByAppendingComponent(blobDirectory, name);
        auto linkCount = FileSystem::hardLinkCount(path);
        // No clients left for this blob.
        if (linkCount && *linkCount == 1)
            FileSystem::deleteFile(path);
        else
            m_approximateSize += FileSystem::fileSize(path).value_or(0);
    });

    LOG(NetworkCacheStorage, "(NetworkProcess) blob synchronization completed approximateSize=%zu", approximateSize());
}

String BlobStorage::blobPathForHash(const SHA1::Digest& hash) const
{
    auto hashAsString = SHA1::hexDigest(hash);
    return FileSystem::pathByAppendingComponent(blobDirectoryPathIsolatedCopy(), StringView::fromLatin1(hashAsString.data()));
}

BlobStorage::Blob BlobStorage::add(const String& path, const Data& data)
{
    ASSERT(!RunLoop::isMain());

    auto hash = computeSHA1(data, m_salt);
    if (data.isEmpty())
        return { data, hash };

    String blobPath = blobPathForHash(hash);
    
    FileSystem::deleteFile(path);

    bool blobExists = FileSystem::fileExists(blobPath);
    if (blobExists) {
        if (FileSystem::makeSafeToUseMemoryMapForPath(blobPath)) {
            auto existingData = mapFile(blobPath);
            if (bytesEqual(existingData, data)) {
                if (!FileSystem::hardLink(blobPath, path))
                    WTFLogAlways("Failed to create hard link from %s to %s", blobPath.utf8().data(), path.utf8().data());
                return { existingData, hash };
            }
        }
        FileSystem::deleteFile(blobPath);
    }

    auto mappedData = data.mapToFile(blobPath);
    if (mappedData.isNull())
        return { };

    if (!FileSystem::hardLink(blobPath, path))
        WTFLogAlways("Failed to create hard link from %s to %s", blobPath.utf8().data(), path.utf8().data());

    m_approximateSize += mappedData.size();

    return { mappedData, hash };
}

BlobStorage::Blob BlobStorage::get(const String& path)
{
    ASSERT(!RunLoop::isMain());

    auto data = mapFile(path);

    return { data, computeSHA1(data, m_salt) };
}

void BlobStorage::remove(const String& path)
{
    ASSERT(!RunLoop::isMain());

    FileSystem::deleteFile(path);
}

unsigned BlobStorage::shareCount(const String& path)
{
    ASSERT(!RunLoop::isMain());

    auto linkCount = FileSystem::hardLinkCount(path);
    if (!linkCount)
        return 0;
    // Link count is 2 in the single client case (the blob file and a link).
    return *linkCount - 1;
}

}
}
