component/libfuse/native_file_io.h (130 lines of code) (raw):
/*
_____ _____ _____ ____ ______ _____ ------
| | | | | | | | | | | | |
| | | | | | | | | | | | |
| --- | | | | |-----| |---- | | |-----| |----- ------
| | | | | | | | | | | | |
| ____| |_____ | ____| | ____| | |_____| _____| |_____ |_____
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright © 2020-2023 Microsoft Corporation. All rights reserved.
Author : <blobfusedev@microsoft.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/
#ifndef __NATIVE_FILE_IO_H__
#define __NATIVE_FILE_IO_H__
// Every read-write operation is counted and after N operations send a call up to update cache policy
#define CACHE_UPDATE_COUNTER 100
// Structure that describes file-handle object returned back to libfuse
typedef struct {
uint64_t fd; // Unix FD for this file
uint64_t obj; // Handlemap.Handle object representing this handle
uint16_t cnt; // Number of read-write operations done on this handle
uint8_t dirty; // A write operation was performed on this handle
} file_handle_t;
// allocate_native_file_object : Allocate a native C-struct to hold handle map object and unix FD
static file_handle_t* allocate_native_file_object(uint64_t fd, uint64_t obj, uint64_t file_size)
{
// Called on open / create calls from libfuse component
file_handle_t* fobj = (file_handle_t*)malloc(sizeof(file_handle_t));
if (fobj) {
memset(fobj, 0, sizeof(file_handle_t));
fobj->fd = fd;
fobj->obj = obj;
}
return fobj;
}
// release_native_file_object : Release the native C-struct for handle
static void release_native_file_object(fuse_file_info_t* fi)
{
// Called on close operation from libfuse component
file_handle_t* handle_obj = (file_handle_t*)fi->fh;
if (handle_obj) {
free(handle_obj);
}
}
// native_pread : Do pread on file directly without involving any Go code
static int native_pread(char *path, char *buf, size_t size, off_t offset, file_handle_t* handle_obj)
{
errno = 0;
int res = pread(handle_obj->fd, buf, size, offset);
if (res == -1)
res = -errno;
#if 0
handle_obj->cnt++;
if (!(handle_obj->cnt % CACHE_UPDATE_COUNTER)) {
// Time to send a call up to update the cache
blobfuse_cache_update(path);
handle_obj->cnt = 0;
}
#endif
return res;
}
// native_pwrite : Do pwrite on file directly without involving any Go code
static int native_pwrite(char *path, char *buf, size_t size, off_t offset, file_handle_t* handle_obj)
{
errno = 0;
int res = pwrite(handle_obj->fd, buf, size, offset);
if (res == -1)
res = -errno;
// Increment the operation counter and mark a write was done on this handle
handle_obj->dirty = 1;
handle_obj->cnt++;
if (!(handle_obj->cnt % CACHE_UPDATE_COUNTER)) {
// Time to send a call up to update the cache
blobfuse_cache_update(path);
handle_obj->cnt = 0;
}
return res;
}
// native_read_file : Read callback to decide whether to natively read or punt call to Go code
static int native_read_file(char *path, char *buf, size_t size, off_t offset, fuse_file_info_t *fi)
{
file_handle_t* handle_obj = (file_handle_t*)fi->fh;
#if 0
return libfuse_read(path, buf, size, offset, fi);
#endif
if (handle_obj->fd == 0) {
return libfuse_read(path, buf, size, offset, fi);
}
return native_pread(path, buf, size, offset, handle_obj);
}
// native_write_file : Write callback to decide whether to natively write or punt call to Go code
static int native_write_file(char *path, char *buf, size_t size, off_t offset, fuse_file_info_t *fi)
{
file_handle_t* handle_obj = (file_handle_t*)fi->fh;
#if 0
return libfuse_write(path, buf, size, offset, fi);
#endif
if (handle_obj->fd == 0) {
return libfuse_write(path, buf, size, offset, fi);
}
return native_pwrite(path, buf, size, offset, handle_obj);
}
// native_flush_file : Flush the file natively and call flush up in the pipeline to upload this file
static int native_flush_file(char *path, fuse_file_info_t *fi)
{
file_handle_t* handle_obj = (file_handle_t*)fi->fh;
int ret = libfuse_flush(path, fi);
if (ret == 0) {
// As file is flushed and uploaded, reset the dirty bit here
handle_obj->dirty = 0;
}
return ret;
}
#ifdef ENABLE_READ_AHEAD
// read_ahead_handler : Method to serve read call from read-ahead buffer if possible
static int read_ahead_handler(char *path, char *buf, size_t size, off_t offset, file_handle_t* handle_obj)
{
int new_read = 0;
/* Random read determination logic :
handle_obj->random_reads : is counter used for this
- For every sequential read decrement this counter by 1
- For every new read from physical file (random read or buffer refresh) increment the counter by 2
- At any point if the counter value is > 5 then caller will disable read-ahead on this handle
: If file is being read sequentially then counter will be negative and a buffer refresh will not skew the counter much
: If file is read sequentially and later application moves to random read, at some point we will disable read-ahead logic
: If file is read randomly then counter will be positive and we will disable read-ahead after 2-3 reads
: If file is read randomly first and then sequentially then we assume it will be random read and disable the read-ahead
*/
if ((handle_obj->buff_start == 0 && handle_obj->buff_end == 0) ||
offset < handle_obj->buff_start ||
offset >= handle_obj->buff_end)
{
// Either this is first read call or read is outside the current buffer boundary
// So we need to read a fresh buffer from physical file
new_read = 1;
handle_obj->random_reads += 2;
} else {
handle_obj->random_reads--;
}
if (new_read) {
// We need to refresh the data from file
int read = native_pread(path, handle_obj->buff, RA_BLOCK_SIZE, offset, handle_obj);
FILE *fp = fopen("blobfuse2_nat.log", "a");
if (fp) {
fprintf(fp, "File %s, Offset %ld, size %ld, new read %d\n",
path, offset, size, read);
fclose(fp);
}
if (read <= 0) {
// Error or EOF reached to just return 0 now
return read;
}
handle_obj->buff_start = offset;
handle_obj->buff_end = offset + read;
}
// Buffer is populated so calculate how much to copy from here now.
int start = offset - handle_obj->buff_start;
int left = (handle_obj->buff_end - offset);
int copy = (size > left) ? left : size;
FILE *fp = fopen("blobfuse2_nat.log", "a");
if (fp) {
fprintf(fp, "File %s, Offset %ld, size %ld, buff start %ld, buff end %ld, start %d, left %d, copy %d\n",
path, offset, size, handle_obj->buff_start, handle_obj->buff_end, start, left, copy);
fclose(fp);
}
memcpy(buf, (handle_obj->buff + start), copy);
if (copy < size) {
// Less then request data was copied so read from next offset again
// We need to handle this here because if we return less then size fuse is not asking from
// correct offset in next read, it just goes to offset + size only.
copy += read_ahead_handler(path, (buf + copy), (size - copy), (offset + copy), handle_obj);
}
return copy;
}
#endif
#endif //__NATIVE_FILE_IO_H__