root/src/tools/fs_shell/partition_support.cpp
/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "compatibility.h"

#include "partition_support.h"

#include <errno.h>
#include <unistd.h>

#include <list>

#include "fssh_errno.h"
#include "fssh_stat.h"
#include "fssh_unistd.h"
#include "stat_priv.h"


using namespace FSShell;


namespace FSShell {


struct FileRestriction {
        FileRestriction(fssh_dev_t device, fssh_ino_t node, fssh_off_t startOffset,
                        fssh_off_t endOffset)
                :
                device(device),
                node(node),
                startOffset(startOffset),
                endOffset(endOffset)
        {
        }

        fssh_dev_t      device;
        fssh_ino_t      node;
        fssh_off_t      startOffset;
        fssh_off_t      endOffset;
};


typedef std::list<FileRestriction*> FileRestrictionList;

static FileRestrictionList sFileRestrictions;


static FileRestriction*
find_file_restriction(fssh_dev_t device, fssh_ino_t node)
{
        for (FileRestrictionList::iterator it = sFileRestrictions.begin();
                        it != sFileRestrictions.end(); ++it) {
                FileRestriction* restriction = *it;
                if (restriction->device == device && restriction->node == node)
                        return restriction;
        }

        return NULL;
}


static FileRestriction*
find_file_restriction(int fd)
{
        struct fssh_stat st;
        if (unrestricted_fstat(fd, &st) < 0)
                return NULL;

        return find_file_restriction(st.fssh_st_dev, st.fssh_st_ino);
}


void
add_file_restriction(const char* fileName, fssh_off_t startOffset,
        fssh_off_t endOffset)
{
        struct fssh_stat st;
        if (unrestricted_stat(fileName, &st) < 0)
                return;

        fssh_dev_t device = st.fssh_st_dev;
        fssh_ino_t node = st.fssh_st_ino;

        FileRestriction* restriction = find_file_restriction(device, node);
        if (restriction)
                return;

        if (endOffset < 0)
                endOffset = st.fssh_st_size;

        restriction = new FileRestriction(device, node, startOffset, endOffset);
        sFileRestrictions.push_back(restriction);
}


void
restricted_file_opened(int fd)
{
        FileRestriction* restriction = find_file_restriction(fd);
        if (!restriction)
                return;

        lseek(fd, restriction->startOffset, SEEK_SET);
}


void
restricted_file_duped(int oldFD, int newFD)
{
}


void
restricted_file_closed(int fd)
{
}


int
restricted_file_restrict_io(int fd, fssh_off_t& pos, fssh_off_t size)
{
        FileRestriction* restriction = find_file_restriction(fd);
        if (!restriction)
                return 0;

        if (pos < 0) {
                pos = lseek(fd, 0, SEEK_CUR);
                if (pos < 0)
                        return -1;
        } else
                pos += restriction->startOffset;

        if (pos < restriction->startOffset || pos > restriction->endOffset) {
                fssh_set_errno(B_BAD_VALUE);
                return -1;
        }

        fssh_off_t maxSize = restriction->endOffset - pos;
        if (size > maxSize)
                size = maxSize;

        return 0;
}


void
restricted_file_restrict_stat(struct fssh_stat* st)
{
        FileRestriction* restriction = find_file_restriction(st->fssh_st_dev,
                st->fssh_st_ino);
        if (!restriction)
                return;

        st->fssh_st_size = restriction->endOffset - restriction->startOffset;
}


static int
to_platform_seek_mode(int fsshWhence)
{
        switch (fsshWhence) {
                case FSSH_SEEK_CUR:
                        return SEEK_CUR;
                case FSSH_SEEK_END:
                        return SEEK_END;
                case FSSH_SEEK_SET:
                default:
                        return SEEK_SET;
        }
}


}       // namespace FSShell


fssh_off_t
fssh_lseek(int fd, fssh_off_t offset, int whence)
{
        FileRestriction* restriction = find_file_restriction(fd);
        if (!restriction)
                return lseek(fd, offset, to_platform_seek_mode(whence));

        fssh_off_t pos;

        switch (whence) {
                case FSSH_SEEK_CUR:
                {
                        pos = lseek(fd, 0, SEEK_CUR);
                        if (pos < 0)
                                return pos;
                        pos += offset;
                        break;
                }
                case FSSH_SEEK_END:
                        pos = restriction->endOffset + offset;
                        break;
                case FSSH_SEEK_SET:
                default:
                        pos = restriction->startOffset + offset;
                        break;
        }

        if (pos < restriction->startOffset) {
                fssh_set_errno(B_BAD_VALUE);
                return -1;
        }

        pos = lseek(fd, pos, SEEK_SET);
        if (pos >= 0)
                pos -= restriction->startOffset;

        return pos;
}