/*
* 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/bufferedWriter.c#6 $
*/
#include "bufferedWriter.h"
#include "compiler.h"
#include "errors.h"
#include "ioFactory.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "numeric.h"
struct bufferedWriter {
#ifdef __KERNEL__
// IOFactory owning the block device
IOFactory *bw_factory;
// The dm_bufio_client to write to
struct dm_bufio_client *bw_client;
// The current dm_buffer
struct dm_buffer *bw_buffer;
// The number of blocks that can be written to
sector_t bw_limit;
// Number of the current block
sector_t bw_blockNumber;
#else
// Region to write to
IORegion *bw_region;
// Number of the current block
uint64_t bw_blockNumber;
#endif
// Start of the buffer
byte *bw_start;
// End of the data written to the buffer
byte *bw_pointer;
// Error code
int bw_error;
// Have writes been done?
bool bw_used;
};
#ifdef __KERNEL__
/*****************************************************************************/
__attribute__((warn_unused_result))
int prepareNextBuffer(BufferedWriter *bw)
{
if (bw->bw_blockNumber >= bw->bw_limit) {
bw->bw_error = UDS_OUT_OF_RANGE;
return UDS_OUT_OF_RANGE;
}
struct dm_buffer *buffer = NULL;
void *data = dm_bufio_new(bw->bw_client, bw->bw_blockNumber, &buffer);
if (IS_ERR(data)) {
bw->bw_error = -PTR_ERR(data);
return bw->bw_error;
}
bw->bw_buffer = buffer;
bw->bw_start = data;
bw->bw_pointer = data;
return UDS_SUCCESS;
}
/*****************************************************************************/
int flushPreviousBuffer(BufferedWriter *bw)
{
if (bw->bw_buffer != NULL) {
if (bw->bw_error == UDS_SUCCESS) {
size_t avail = spaceRemainingInWriteBuffer(bw);
if (avail > 0) {
memset(bw->bw_pointer, 0, avail);
}
dm_bufio_mark_buffer_dirty(bw->bw_buffer);
}
dm_bufio_release(bw->bw_buffer);
bw->bw_buffer = NULL;
bw->bw_start = NULL;
bw->bw_pointer = NULL;
bw->bw_blockNumber++;
}
return bw->bw_error;
}
#endif
/*****************************************************************************/
#ifdef __KERNEL__
int makeBufferedWriter(IOFactory *factory,
struct dm_bufio_client *client,
sector_t blockLimit,
BufferedWriter **writerPtr)
{
BufferedWriter *writer;
int result = ALLOCATE(1, BufferedWriter, "buffered writer", &writer);
if (result != UDS_SUCCESS) {
return result;
}
*writer = (BufferedWriter) {
.bw_factory = factory,
.bw_client = client,
.bw_buffer = NULL,
.bw_limit = blockLimit,
.bw_start = NULL,
.bw_pointer = NULL,
.bw_blockNumber = 0,
.bw_error = UDS_SUCCESS,
.bw_used = false,
};
getIOFactory(factory);
*writerPtr = writer;
return UDS_SUCCESS;
}
#else
int makeBufferedWriter(IORegion *region, BufferedWriter **writerPtr)
{
byte *data;
int result = ALLOCATE_IO_ALIGNED(UDS_BLOCK_SIZE, byte,
"buffer writer buffer", &data);
if (result != UDS_SUCCESS) {
return result;
}
BufferedWriter *writer;
result = ALLOCATE(1, BufferedWriter, "buffered writer", &writer);
if (result != UDS_SUCCESS) {
FREE(data);
return result;
}
*writer = (BufferedWriter) {
.bw_region = region,
.bw_start = data,
.bw_pointer = data,
.bw_blockNumber = 0,
.bw_error = UDS_SUCCESS,
.bw_used = false,
};
getIORegion(region);
*writerPtr = writer;
return UDS_SUCCESS;
}
#endif
/*****************************************************************************/
void freeBufferedWriter(BufferedWriter *bw)
{
if (bw == NULL) {
return;
}
#ifdef __KERNEL__
flushPreviousBuffer(bw);
int result = -dm_bufio_write_dirty_buffers(bw->bw_client);
#else
int result = syncRegionContents(bw->bw_region);
#endif
if (result != UDS_SUCCESS) {
logWarningWithStringError(result, "%s cannot sync storage", __func__);
}
#ifdef __KERNEL__
dm_bufio_client_destroy(bw->bw_client);
putIOFactory(bw->bw_factory);
#else
putIORegion(bw->bw_region);
FREE(bw->bw_start);
#endif
FREE(bw);
}
/*****************************************************************************/
static INLINE size_t spaceUsedInBuffer(BufferedWriter *bw)
{
return bw->bw_pointer - bw->bw_start;
}
/*****************************************************************************/
size_t spaceRemainingInWriteBuffer(BufferedWriter *bw)
{
return UDS_BLOCK_SIZE - spaceUsedInBuffer(bw);
}
/*****************************************************************************/
int writeToBufferedWriter(BufferedWriter *bw, const void *data, size_t len)
{
if (bw->bw_error != UDS_SUCCESS) {
return bw->bw_error;
}
const byte *dp = data;
int result = UDS_SUCCESS;
while ((len > 0) && (result == UDS_SUCCESS)) {
#ifdef __KERNEL__
if (bw->bw_buffer == NULL) {
result = prepareNextBuffer(bw);
continue;
}
#endif
size_t avail = spaceRemainingInWriteBuffer(bw);
size_t chunk = minSizeT(len, avail);
memcpy(bw->bw_pointer, dp, chunk);
len -= chunk;
dp += chunk;
bw->bw_pointer += chunk;
if (spaceRemainingInWriteBuffer(bw) == 0) {
result = flushBufferedWriter(bw);
}
}
bw->bw_used = true;
return result;
}
/*****************************************************************************/
int writeZerosToBufferedWriter(BufferedWriter *bw, size_t len)
{
if (bw->bw_error != UDS_SUCCESS) {
return bw->bw_error;
}
int result = UDS_SUCCESS;
while ((len > 0) && (result == UDS_SUCCESS)) {
#ifdef __KERNEL__
if (bw->bw_buffer == NULL) {
result = prepareNextBuffer(bw);
continue;
}
#endif
size_t avail = spaceRemainingInWriteBuffer(bw);
size_t chunk = minSizeT(len, avail);
memset(bw->bw_pointer, 0, chunk);
len -= chunk;
bw->bw_pointer += chunk;
if (spaceRemainingInWriteBuffer(bw) == 0) {
result = flushBufferedWriter(bw);
}
}
bw->bw_used = true;
return result;
}
/*****************************************************************************/
int flushBufferedWriter(BufferedWriter *bw)
{
if (bw->bw_error != UDS_SUCCESS) {
return bw->bw_error;
}
#ifdef __KERNEL__
return flushPreviousBuffer(bw);
#else
size_t n = spaceUsedInBuffer(bw);
if (n > 0) {
int result = writeToRegion(bw->bw_region,
bw->bw_blockNumber * UDS_BLOCK_SIZE,
bw->bw_start, UDS_BLOCK_SIZE, n);
if (result != UDS_SUCCESS) {
return bw->bw_error = result;
} else {
bw->bw_pointer = bw->bw_start;
bw->bw_blockNumber++;
}
}
return UDS_SUCCESS;
#endif
}
/*****************************************************************************/
bool wasBufferedWriterUsed(const BufferedWriter *bw)
{
return bw->bw_used;
}
/*****************************************************************************/
void noteBufferedWriterUsed(BufferedWriter *bw)
{
bw->bw_used = true;
}