386 lines
7.8 KiB
C
386 lines
7.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2018 HUAWEI, Inc.
|
|
* http://www.huawei.com/
|
|
* Created by Li Guifu <bluce.liguifu@huawei.com>
|
|
*/
|
|
#ifndef _LARGEFILE64_SOURCE
|
|
#define _LARGEFILE64_SOURCE
|
|
#endif
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include "erofs/io.h"
|
|
#ifdef HAVE_LINUX_FS_H
|
|
#include <linux/fs.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_FALLOC_H
|
|
#include <linux/falloc.h>
|
|
#endif
|
|
|
|
#define EROFS_MODNAME "erofs_io"
|
|
#include "erofs/print.h"
|
|
|
|
static const char *erofs_devname;
|
|
int erofs_devfd = -1;
|
|
static u64 erofs_devsz;
|
|
static unsigned int erofs_nblobs, erofs_blobfd[256];
|
|
|
|
int dev_get_blkdev_size(int fd, u64 *bytes)
|
|
{
|
|
errno = ENOTSUP;
|
|
#ifdef BLKGETSIZE64
|
|
if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
|
|
return 0;
|
|
#endif
|
|
|
|
#ifdef BLKGETSIZE
|
|
{
|
|
unsigned long size;
|
|
if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
|
|
*bytes = ((u64)size << 9);
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
return -errno;
|
|
}
|
|
|
|
void dev_close(void)
|
|
{
|
|
close(erofs_devfd);
|
|
erofs_devname = NULL;
|
|
erofs_devfd = -1;
|
|
erofs_devsz = 0;
|
|
}
|
|
|
|
int dev_open(const char *dev)
|
|
{
|
|
struct stat st;
|
|
int fd, ret;
|
|
|
|
fd = open(dev, O_RDWR | O_CREAT | O_BINARY, 0644);
|
|
if (fd < 0) {
|
|
erofs_err("failed to open(%s).", dev);
|
|
return -errno;
|
|
}
|
|
|
|
ret = fstat(fd, &st);
|
|
if (ret) {
|
|
erofs_err("failed to fstat(%s).", dev);
|
|
close(fd);
|
|
return -errno;
|
|
}
|
|
|
|
switch (st.st_mode & S_IFMT) {
|
|
case S_IFBLK:
|
|
ret = dev_get_blkdev_size(fd, &erofs_devsz);
|
|
if (ret) {
|
|
erofs_err("failed to get block device size(%s).", dev);
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
erofs_devsz = round_down(erofs_devsz, EROFS_BLKSIZ);
|
|
break;
|
|
case S_IFREG:
|
|
ret = ftruncate(fd, 0);
|
|
if (ret) {
|
|
erofs_err("failed to ftruncate(%s).", dev);
|
|
close(fd);
|
|
return -errno;
|
|
}
|
|
/* INT64_MAX is the limit of kernel vfs */
|
|
erofs_devsz = INT64_MAX;
|
|
break;
|
|
default:
|
|
erofs_err("bad file type (%s, %o).", dev, st.st_mode);
|
|
close(fd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
erofs_devname = dev;
|
|
erofs_devfd = fd;
|
|
|
|
erofs_info("successfully to open %s", dev);
|
|
return 0;
|
|
}
|
|
|
|
void blob_closeall(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < erofs_nblobs; ++i)
|
|
close(erofs_blobfd[i]);
|
|
erofs_nblobs = 0;
|
|
}
|
|
|
|
int blob_open_ro(const char *dev)
|
|
{
|
|
int fd = open(dev, O_RDONLY | O_BINARY);
|
|
|
|
if (fd < 0) {
|
|
erofs_err("failed to open(%s).", dev);
|
|
return -errno;
|
|
}
|
|
|
|
erofs_blobfd[erofs_nblobs] = fd;
|
|
erofs_info("successfully to open blob%u %s", erofs_nblobs, dev);
|
|
++erofs_nblobs;
|
|
return 0;
|
|
}
|
|
|
|
/* XXX: temporary soluation. Disk I/O implementation needs to be refactored. */
|
|
int dev_open_ro(const char *dev)
|
|
{
|
|
int fd = open(dev, O_RDONLY | O_BINARY);
|
|
|
|
if (fd < 0) {
|
|
erofs_err("failed to open(%s).", dev);
|
|
return -errno;
|
|
}
|
|
|
|
erofs_devfd = fd;
|
|
erofs_devname = dev;
|
|
erofs_devsz = INT64_MAX;
|
|
return 0;
|
|
}
|
|
|
|
u64 dev_length(void)
|
|
{
|
|
return erofs_devsz;
|
|
}
|
|
|
|
int dev_write(const void *buf, u64 offset, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
if (cfg.c_dry_run)
|
|
return 0;
|
|
|
|
if (!buf) {
|
|
erofs_err("buf is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (offset >= erofs_devsz || len > erofs_devsz ||
|
|
offset > erofs_devsz - len) {
|
|
erofs_err("Write posion[%" PRIu64 ", %zd] is too large beyond the end of device(%" PRIu64 ").",
|
|
offset, len, erofs_devsz);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef HAVE_PWRITE64
|
|
ret = pwrite64(erofs_devfd, buf, len, (off64_t)offset);
|
|
#else
|
|
ret = pwrite(erofs_devfd, buf, len, (off_t)offset);
|
|
#endif
|
|
if (ret != (int)len) {
|
|
if (ret < 0) {
|
|
erofs_err("Failed to write data into device - %s:[%" PRIu64 ", %zd].",
|
|
erofs_devname, offset, len);
|
|
return -errno;
|
|
}
|
|
|
|
erofs_err("Writing data into device - %s:[%" PRIu64 ", %zd] - was truncated.",
|
|
erofs_devname, offset, len);
|
|
return -ERANGE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int dev_fillzero(u64 offset, size_t len, bool padding)
|
|
{
|
|
static const char zero[EROFS_BLKSIZ] = {0};
|
|
int ret;
|
|
|
|
if (cfg.c_dry_run)
|
|
return 0;
|
|
|
|
#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
|
|
if (!padding && fallocate(erofs_devfd, FALLOC_FL_PUNCH_HOLE |
|
|
FALLOC_FL_KEEP_SIZE, offset, len) >= 0)
|
|
return 0;
|
|
#endif
|
|
while (len > EROFS_BLKSIZ) {
|
|
ret = dev_write(zero, offset, EROFS_BLKSIZ);
|
|
if (ret)
|
|
return ret;
|
|
len -= EROFS_BLKSIZ;
|
|
offset += EROFS_BLKSIZ;
|
|
}
|
|
return dev_write(zero, offset, len);
|
|
}
|
|
|
|
int dev_fsync(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = fsync(erofs_devfd);
|
|
if (ret) {
|
|
erofs_err("Could not fsync device!!!");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int dev_resize(unsigned int blocks)
|
|
{
|
|
int ret;
|
|
struct stat st;
|
|
u64 length;
|
|
|
|
if (cfg.c_dry_run || erofs_devsz != INT64_MAX)
|
|
return 0;
|
|
|
|
ret = fstat(erofs_devfd, &st);
|
|
if (ret) {
|
|
erofs_err("failed to fstat.");
|
|
return -errno;
|
|
}
|
|
|
|
length = (u64)blocks * EROFS_BLKSIZ;
|
|
if (st.st_size == length)
|
|
return 0;
|
|
if (st.st_size > length)
|
|
return ftruncate(erofs_devfd, length);
|
|
|
|
length = length - st.st_size;
|
|
#if defined(HAVE_FALLOCATE)
|
|
if (fallocate(erofs_devfd, 0, st.st_size, length) >= 0)
|
|
return 0;
|
|
#endif
|
|
return dev_fillzero(st.st_size, length, true);
|
|
}
|
|
|
|
int dev_read(int device_id, void *buf, u64 offset, size_t len)
|
|
{
|
|
int ret, fd;
|
|
|
|
if (cfg.c_dry_run)
|
|
return 0;
|
|
|
|
if (!buf) {
|
|
erofs_err("buf is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!device_id) {
|
|
fd = erofs_devfd;
|
|
} else {
|
|
if (device_id > erofs_nblobs) {
|
|
erofs_err("invalid device id %d", device_id);
|
|
return -ENODEV;
|
|
}
|
|
fd = erofs_blobfd[device_id - 1];
|
|
}
|
|
|
|
#ifdef HAVE_PREAD64
|
|
ret = pread64(fd, buf, len, (off64_t)offset);
|
|
#else
|
|
ret = pread(fd, buf, len, (off_t)offset);
|
|
#endif
|
|
if (ret != (int)len) {
|
|
erofs_err("Failed to read data from device - %s:[%" PRIu64 ", %zd].",
|
|
erofs_devname, offset, len);
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t __erofs_copy_file_range(int fd_in, erofs_off_t *off_in,
|
|
int fd_out, erofs_off_t *off_out,
|
|
size_t length)
|
|
{
|
|
size_t copied = 0;
|
|
char buf[8192];
|
|
|
|
/*
|
|
* Main copying loop. The buffer size is arbitrary and is a
|
|
* trade-off between stack size consumption, cache usage, and
|
|
* amortization of system call overhead.
|
|
*/
|
|
while (length > 0) {
|
|
size_t to_read;
|
|
ssize_t read_count;
|
|
char *end, *p;
|
|
|
|
to_read = min_t(size_t, length, sizeof(buf));
|
|
#ifdef HAVE_PREAD64
|
|
read_count = pread64(fd_in, buf, to_read, *off_in);
|
|
#else
|
|
read_count = pread(fd_in, buf, to_read, *off_in);
|
|
#endif
|
|
if (read_count == 0)
|
|
/* End of file reached prematurely. */
|
|
return copied;
|
|
if (read_count < 0) {
|
|
/* Report the number of bytes copied so far. */
|
|
if (copied > 0)
|
|
return copied;
|
|
return -1;
|
|
}
|
|
*off_in += read_count;
|
|
|
|
/* Write the buffer part which was read to the destination. */
|
|
end = buf + read_count;
|
|
for (p = buf; p < end; ) {
|
|
ssize_t write_count;
|
|
|
|
#ifdef HAVE_PWRITE64
|
|
write_count = pwrite64(fd_out, p, end - p, *off_out);
|
|
#else
|
|
write_count = pwrite(fd_out, p, end - p, *off_out);
|
|
#endif
|
|
if (write_count < 0) {
|
|
/*
|
|
* Adjust the input read position to match what
|
|
* we have written, so that the caller can pick
|
|
* up after the error.
|
|
*/
|
|
size_t written = p - buf;
|
|
/*
|
|
* NB: This needs to be signed so that we can
|
|
* form the negative value below.
|
|
*/
|
|
ssize_t overread = read_count - written;
|
|
|
|
*off_in -= overread;
|
|
/* Report the number of bytes copied so far. */
|
|
if (copied + written > 0)
|
|
return copied + written;
|
|
return -1;
|
|
}
|
|
p += write_count;
|
|
*off_out += write_count;
|
|
} /* Write loop. */
|
|
copied += read_count;
|
|
length -= read_count;
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
ssize_t erofs_copy_file_range(int fd_in, erofs_off_t *off_in,
|
|
int fd_out, erofs_off_t *off_out,
|
|
size_t length)
|
|
{
|
|
#ifdef HAVE_COPY_FILE_RANGE
|
|
off64_t off64_in = *off_in, off64_out = *off_out;
|
|
ssize_t ret;
|
|
|
|
ret = copy_file_range(fd_in, &off64_in, fd_out, &off64_out,
|
|
length, 0);
|
|
if (ret >= 0)
|
|
goto out;
|
|
if (errno != ENOSYS) {
|
|
ret = -errno;
|
|
out:
|
|
*off_in = off64_in;
|
|
*off_out = off64_out;
|
|
return ret;
|
|
}
|
|
#endif
|
|
return __erofs_copy_file_range(fd_in, off_in, fd_out, off_out, length);
|
|
}
|