/*
* Copyright (c) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* $Id: //eng/uds-releases/jasper/src/uds/bufferedReader.c#5 $
*/
#include "bufferedReader.h"
#include "compiler.h"
#include "ioFactory.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "numeric.h"
#ifndef __KERNEL__
/*
* Define sector_t. The kernel really wants us to use it. The code becomes
* ugly if we need to #ifdef every usage of sector_t. Note that the of #define
* means that even if a user mode include typedefs sector_t, it will not affect
* this module.
*/
#define sector_t uint64_t
#endif
struct bufferedReader {
#ifdef __KERNEL__
// IOFactory owning the block device
IOFactory *br_factory;
// The dm_bufio_client to read from
struct dm_bufio_client *br_client;
// The current dm_buffer
struct dm_buffer *br_buffer;
// The number of blocks that can be read from
sector_t br_limit;
// Number of the current block
sector_t br_blockNumber;
#else
// Region to read from
IORegion *br_region;
// Number of the current block
uint64_t br_blockNumber;
#endif
// Start of the buffer
byte *br_start;
// End of the data read from the buffer
byte *br_pointer;
};
#ifdef __KERNEL__
/*****************************************************************************/
static void readAhead(BufferedReader *br, sector_t blockNumber)
{
if (blockNumber < br->br_limit) {
enum { MAX_READ_AHEAD = 4 };
size_t readAhead = minSizeT(MAX_READ_AHEAD, br->br_limit - blockNumber);
dm_bufio_prefetch(br->br_client, blockNumber, readAhead);
}
}
#endif
/*****************************************************************************/
#ifdef __KERNEL__
int makeBufferedReader(IOFactory *factory,
struct dm_bufio_client *client,
sector_t blockLimit,
BufferedReader **readerPtr)
{
BufferedReader *reader = NULL;
int result = ALLOCATE(1, BufferedReader, "buffered reader", &reader);
if (result != UDS_SUCCESS) {
return result;
}
*reader = (BufferedReader) {
.br_factory = factory,
.br_client = client,
.br_buffer = NULL,
.br_limit = blockLimit,
.br_blockNumber = 0,
.br_start = NULL,
.br_pointer = NULL,
};
readAhead(reader,0);
getIOFactory(factory);
*readerPtr = reader;
return UDS_SUCCESS;
}
#else
int makeBufferedReader(IORegion *region, BufferedReader **readerPtr)
{
byte *data;
int result = ALLOCATE_IO_ALIGNED(UDS_BLOCK_SIZE, byte,
"buffer writer buffer", &data);
if (result != UDS_SUCCESS) {
return result;
}
BufferedReader *reader = NULL;
result = ALLOCATE(1, BufferedReader, "buffered reader", &reader);
if (result != UDS_SUCCESS) {
FREE(data);
return result;
}
*reader = (BufferedReader) {
.br_region = region,
.br_blockNumber = 0,
.br_start = data,
.br_pointer = NULL,
};
getIORegion(region);
*readerPtr = reader;
return UDS_SUCCESS;
}
#endif
/*****************************************************************************/
void freeBufferedReader(BufferedReader *br)
{
if (br == NULL) {
return;
}
#ifdef __KERNEL__
if (br->br_buffer != NULL) {
dm_bufio_release(br->br_buffer);
}
dm_bufio_client_destroy(br->br_client);
putIOFactory(br->br_factory);
#else
putIORegion(br->br_region);
FREE(br->br_start);
#endif
FREE(br);
}
/*****************************************************************************/
static int positionReader(BufferedReader *br,
sector_t blockNumber,
off_t offset)
{
if ((br->br_pointer == NULL) || (blockNumber != br->br_blockNumber)) {
#ifdef __KERNEL__
if (blockNumber >= br->br_limit) {
return UDS_OUT_OF_RANGE;
}
if (br->br_buffer != NULL) {
dm_bufio_release(br->br_buffer);
br->br_buffer = NULL;
}
struct dm_buffer *buffer = NULL;
void *data = dm_bufio_read(br->br_client, blockNumber, &buffer);
if (IS_ERR(data)) {
return -PTR_ERR(data);
}
br->br_buffer = buffer;
br->br_start = data;
if (blockNumber == br->br_blockNumber + 1) {
readAhead(br, blockNumber + 1);
}
#else
int result = readFromRegion(br->br_region, blockNumber * UDS_BLOCK_SIZE,
br->br_start, UDS_BLOCK_SIZE, NULL);
if (result != UDS_SUCCESS) {
logWarningWithStringError(result, "%s got readFromRegion error",
__func__);
return result;
}
#endif
}
br->br_blockNumber = blockNumber;
br->br_pointer = br->br_start + offset;
return UDS_SUCCESS;
}
/*****************************************************************************/
static size_t bytesRemainingInReadBuffer(BufferedReader *br)
{
return (br->br_pointer == NULL
? 0
: br->br_start + UDS_BLOCK_SIZE - br->br_pointer);
}
/*****************************************************************************/
int readFromBufferedReader(BufferedReader *br, void *data, size_t length)
{
byte *dp = data;
int result = UDS_SUCCESS;
while (length > 0) {
if (bytesRemainingInReadBuffer(br) == 0) {
sector_t blockNumber = br->br_blockNumber;
if (br->br_pointer != NULL) {
++blockNumber;
}
result = positionReader(br, blockNumber, 0);
if (result != UDS_SUCCESS) {
break;
}
}
size_t avail = bytesRemainingInReadBuffer(br);
size_t chunk = minSizeT(length, avail);
memcpy(dp, br->br_pointer, chunk);
length -= chunk;
dp += chunk;
br->br_pointer += chunk;
}
if (((result == UDS_OUT_OF_RANGE) || (result == UDS_END_OF_FILE))
&& (dp - (byte *) data > 0)) {
result = UDS_SHORT_READ;
}
return result;
}
/*****************************************************************************/
int verifyBufferedData(BufferedReader *br,
const void *value,
size_t length)
{
const byte *vp = value;
sector_t startingBlockNumber = br->br_blockNumber;
int startingOffset = br->br_pointer - br->br_start;
while (length > 0) {
if (bytesRemainingInReadBuffer(br) == 0) {
sector_t blockNumber = br->br_blockNumber;
if (br->br_pointer != NULL) {
++blockNumber;
}
int result = positionReader(br, blockNumber, 0);
if (result != UDS_SUCCESS) {
positionReader(br, startingBlockNumber, startingOffset);
return UDS_CORRUPT_FILE;
}
}
size_t avail = bytesRemainingInReadBuffer(br);
size_t chunk = minSizeT(length, avail);
if (memcmp(vp, br->br_pointer, chunk) != 0) {
positionReader(br, startingBlockNumber, startingOffset);
return UDS_CORRUPT_FILE;
}
length -= chunk;
vp += chunk;
br->br_pointer += chunk;
}
return UDS_SUCCESS;
}