You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
985 lines
23 KiB
985 lines
23 KiB
/* |
|
* create_inode.c --- create an inode |
|
* |
|
* Copyright (C) 2014 Robert Yang <liezhi.yang@windriver.com> |
|
* |
|
* %Begin-Header% |
|
* This file may be redistributed under the terms of the GNU library |
|
* General Public License, version 2. |
|
* %End-Header% |
|
*/ |
|
|
|
#define _FILE_OFFSET_BITS 64 |
|
#define _LARGEFILE64_SOURCE 1 |
|
#define _GNU_SOURCE 1 |
|
|
|
#include "config.h" |
|
#include <time.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <unistd.h> |
|
#include <limits.h> /* for PATH_MAX */ |
|
#ifdef HAVE_ATTR_XATTR_H |
|
#include <attr/xattr.h> |
|
#endif |
|
#ifdef HAVE_SYS_IOCTL_H |
|
#include <sys/ioctl.h> |
|
#endif |
|
#ifdef HAVE_SYS_SYSMACROS_H |
|
#include <sys/sysmacros.h> |
|
#endif |
|
|
|
#include <ext2fs/ext2fs.h> |
|
#include <ext2fs/ext2_types.h> |
|
#include <ext2fs/fiemap.h> |
|
|
|
#include "create_inode.h" |
|
#include "support/nls-enable.h" |
|
|
|
/* 64KiB is the minimium blksize to best minimize system call overhead. */ |
|
#define COPY_FILE_BUFLEN 65536 |
|
|
|
static int ext2_file_type(unsigned int mode) |
|
{ |
|
if (LINUX_S_ISREG(mode)) |
|
return EXT2_FT_REG_FILE; |
|
|
|
if (LINUX_S_ISDIR(mode)) |
|
return EXT2_FT_DIR; |
|
|
|
if (LINUX_S_ISCHR(mode)) |
|
return EXT2_FT_CHRDEV; |
|
|
|
if (LINUX_S_ISBLK(mode)) |
|
return EXT2_FT_BLKDEV; |
|
|
|
if (LINUX_S_ISLNK(mode)) |
|
return EXT2_FT_SYMLINK; |
|
|
|
if (LINUX_S_ISFIFO(mode)) |
|
return EXT2_FT_FIFO; |
|
|
|
if (LINUX_S_ISSOCK(mode)) |
|
return EXT2_FT_SOCK; |
|
|
|
return 0; |
|
} |
|
|
|
/* Link an inode number to a directory */ |
|
static errcode_t add_link(ext2_filsys fs, ext2_ino_t parent_ino, |
|
ext2_ino_t ino, const char *name) |
|
{ |
|
struct ext2_inode inode; |
|
errcode_t retval; |
|
|
|
retval = ext2fs_read_inode(fs, ino, &inode); |
|
if (retval) { |
|
com_err(__func__, retval, _("while reading inode %u"), ino); |
|
return retval; |
|
} |
|
|
|
retval = ext2fs_link(fs, parent_ino, name, ino, |
|
ext2_file_type(inode.i_mode)); |
|
if (retval == EXT2_ET_DIR_NO_SPACE) { |
|
retval = ext2fs_expand_dir(fs, parent_ino); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while expanding directory")); |
|
return retval; |
|
} |
|
retval = ext2fs_link(fs, parent_ino, name, ino, |
|
ext2_file_type(inode.i_mode)); |
|
} |
|
if (retval) { |
|
com_err(__func__, retval, _("while linking \"%s\""), name); |
|
return retval; |
|
} |
|
|
|
inode.i_links_count++; |
|
|
|
retval = ext2fs_write_inode(fs, ino, &inode); |
|
if (retval) |
|
com_err(__func__, retval, _("while writing inode %u"), ino); |
|
|
|
return retval; |
|
} |
|
|
|
/* Set the uid, gid, mode and time for the inode */ |
|
static errcode_t set_inode_extra(ext2_filsys fs, ext2_ino_t ino, |
|
struct stat *st) |
|
{ |
|
errcode_t retval; |
|
struct ext2_inode inode; |
|
|
|
retval = ext2fs_read_inode(fs, ino, &inode); |
|
if (retval) { |
|
com_err(__func__, retval, _("while reading inode %u"), ino); |
|
return retval; |
|
} |
|
|
|
inode.i_uid = st->st_uid; |
|
inode.i_gid = st->st_gid; |
|
inode.i_mode |= st->st_mode; |
|
inode.i_atime = st->st_atime; |
|
inode.i_mtime = st->st_mtime; |
|
inode.i_ctime = st->st_ctime; |
|
|
|
retval = ext2fs_write_inode(fs, ino, &inode); |
|
if (retval) |
|
com_err(__func__, retval, _("while writing inode %u"), ino); |
|
return retval; |
|
} |
|
|
|
#ifdef HAVE_LLISTXATTR |
|
static errcode_t set_inode_xattr(ext2_filsys fs, ext2_ino_t ino, |
|
const char *filename) |
|
{ |
|
errcode_t retval, close_retval; |
|
struct ext2_xattr_handle *handle; |
|
ssize_t size, value_size; |
|
char *list = NULL; |
|
int i; |
|
|
|
size = llistxattr(filename, NULL, 0); |
|
if (size == -1) { |
|
retval = errno; |
|
com_err(__func__, retval, _("while listing attributes of \"%s\""), |
|
filename); |
|
return retval; |
|
} else if (size == 0) { |
|
return 0; |
|
} |
|
|
|
retval = ext2fs_xattrs_open(fs, ino, &handle); |
|
if (retval) { |
|
if (retval == EXT2_ET_MISSING_EA_FEATURE) |
|
return 0; |
|
com_err(__func__, retval, _("while opening inode %u"), ino); |
|
return retval; |
|
} |
|
|
|
retval = ext2fs_get_mem(size, &list); |
|
if (retval) { |
|
com_err(__func__, retval, _("while allocating memory")); |
|
goto out; |
|
} |
|
|
|
size = llistxattr(filename, list, size); |
|
if (size == -1) { |
|
retval = errno; |
|
com_err(__func__, retval, _("while listing attributes of \"%s\""), |
|
filename); |
|
goto out; |
|
} |
|
|
|
for (i = 0; i < size; i += strlen(&list[i]) + 1) { |
|
const char *name = &list[i]; |
|
char *value; |
|
|
|
value_size = lgetxattr(filename, name, NULL, 0); |
|
if (value_size == -1) { |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while reading attribute \"%s\" of \"%s\""), |
|
name, filename); |
|
break; |
|
} |
|
|
|
retval = ext2fs_get_mem(value_size, &value); |
|
if (retval) { |
|
com_err(__func__, retval, _("while allocating memory")); |
|
break; |
|
} |
|
|
|
value_size = lgetxattr(filename, name, value, value_size); |
|
if (value_size == -1) { |
|
ext2fs_free_mem(&value); |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while reading attribute \"%s\" of \"%s\""), |
|
name, filename); |
|
break; |
|
} |
|
|
|
retval = ext2fs_xattr_set(handle, name, value, value_size); |
|
ext2fs_free_mem(&value); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while writing attribute \"%s\" to inode %u"), |
|
name, ino); |
|
break; |
|
} |
|
|
|
} |
|
out: |
|
ext2fs_free_mem(&list); |
|
close_retval = ext2fs_xattrs_close(&handle); |
|
if (close_retval) { |
|
com_err(__func__, retval, _("while closing inode %u"), ino); |
|
retval = retval ? retval : close_retval; |
|
} |
|
return retval; |
|
return 0; |
|
} |
|
#else /* HAVE_LLISTXATTR */ |
|
static errcode_t set_inode_xattr(ext2_filsys fs EXT2FS_ATTR((unused)), |
|
ext2_ino_t ino EXT2FS_ATTR((unused)), |
|
const char *filename EXT2FS_ATTR((unused))) |
|
{ |
|
return 0; |
|
} |
|
#endif /* HAVE_LLISTXATTR */ |
|
|
|
#ifndef _WIN32 |
|
/* Make a special files (block and character devices), fifo's, and sockets */ |
|
errcode_t do_mknod_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name, |
|
struct stat *st) |
|
{ |
|
ext2_ino_t ino; |
|
errcode_t retval; |
|
struct ext2_inode inode; |
|
unsigned long devmajor, devminor, mode; |
|
int filetype; |
|
|
|
switch(st->st_mode & S_IFMT) { |
|
case S_IFCHR: |
|
mode = LINUX_S_IFCHR; |
|
filetype = EXT2_FT_CHRDEV; |
|
break; |
|
case S_IFBLK: |
|
mode = LINUX_S_IFBLK; |
|
filetype = EXT2_FT_BLKDEV; |
|
break; |
|
case S_IFIFO: |
|
mode = LINUX_S_IFIFO; |
|
filetype = EXT2_FT_FIFO; |
|
break; |
|
#ifndef _WIN32 |
|
case S_IFSOCK: |
|
mode = LINUX_S_IFSOCK; |
|
filetype = EXT2_FT_SOCK; |
|
break; |
|
#endif |
|
default: |
|
return EXT2_ET_INVALID_ARGUMENT; |
|
} |
|
|
|
retval = ext2fs_new_inode(fs, cwd, 010755, 0, &ino); |
|
if (retval) { |
|
com_err(__func__, retval, _("while allocating inode \"%s\""), |
|
name); |
|
return retval; |
|
} |
|
|
|
#ifdef DEBUGFS |
|
printf("Allocated inode: %u\n", ino); |
|
#endif |
|
retval = ext2fs_link(fs, cwd, name, ino, filetype); |
|
if (retval == EXT2_ET_DIR_NO_SPACE) { |
|
retval = ext2fs_expand_dir(fs, cwd); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while expanding directory")); |
|
return retval; |
|
} |
|
retval = ext2fs_link(fs, cwd, name, ino, filetype); |
|
} |
|
if (retval) { |
|
com_err(name, retval, _("while creating inode \"%s\""), name); |
|
return retval; |
|
} |
|
if (ext2fs_test_inode_bitmap2(fs->inode_map, ino)) |
|
com_err(__func__, 0, "Warning: inode already set"); |
|
ext2fs_inode_alloc_stats2(fs, ino, +1, 0); |
|
memset(&inode, 0, sizeof(inode)); |
|
inode.i_mode = mode; |
|
inode.i_atime = inode.i_ctime = inode.i_mtime = |
|
fs->now ? fs->now : time(0); |
|
|
|
if (filetype != S_IFIFO) { |
|
devmajor = major(st->st_rdev); |
|
devminor = minor(st->st_rdev); |
|
|
|
if ((devmajor < 256) && (devminor < 256)) { |
|
inode.i_block[0] = devmajor * 256 + devminor; |
|
inode.i_block[1] = 0; |
|
} else { |
|
inode.i_block[0] = 0; |
|
inode.i_block[1] = (devminor & 0xff) | (devmajor << 8) | |
|
((devminor & ~0xff) << 12); |
|
} |
|
} |
|
inode.i_links_count = 1; |
|
|
|
retval = ext2fs_write_new_inode(fs, ino, &inode); |
|
if (retval) |
|
com_err(__func__, retval, _("while writing inode %u"), ino); |
|
|
|
return retval; |
|
} |
|
#endif |
|
|
|
/* Make a symlink name -> target */ |
|
errcode_t do_symlink_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name, |
|
char *target, ext2_ino_t root) |
|
{ |
|
char *cp; |
|
ext2_ino_t parent_ino; |
|
errcode_t retval; |
|
|
|
cp = strrchr(name, '/'); |
|
if (cp) { |
|
*cp = 0; |
|
retval = ext2fs_namei(fs, root, cwd, name, &parent_ino); |
|
if (retval) { |
|
com_err(name, retval, 0); |
|
return retval; |
|
} |
|
name = cp+1; |
|
} else |
|
parent_ino = cwd; |
|
|
|
retval = ext2fs_symlink(fs, parent_ino, 0, name, target); |
|
if (retval == EXT2_ET_DIR_NO_SPACE) { |
|
retval = ext2fs_expand_dir(fs, parent_ino); |
|
if (retval) { |
|
com_err("do_symlink_internal", retval, |
|
_("while expanding directory")); |
|
return retval; |
|
} |
|
retval = ext2fs_symlink(fs, parent_ino, 0, name, target); |
|
} |
|
if (retval) |
|
com_err("ext2fs_symlink", retval, |
|
_("while creating symlink \"%s\""), name); |
|
return retval; |
|
} |
|
|
|
/* Make a directory in the fs */ |
|
errcode_t do_mkdir_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name, |
|
ext2_ino_t root) |
|
{ |
|
char *cp; |
|
ext2_ino_t parent_ino; |
|
errcode_t retval; |
|
|
|
|
|
cp = strrchr(name, '/'); |
|
if (cp) { |
|
*cp = 0; |
|
retval = ext2fs_namei(fs, root, cwd, name, &parent_ino); |
|
if (retval) { |
|
com_err(name, retval, _("while looking up \"%s\""), |
|
name); |
|
return retval; |
|
} |
|
name = cp+1; |
|
} else |
|
parent_ino = cwd; |
|
|
|
retval = ext2fs_mkdir(fs, parent_ino, 0, name); |
|
if (retval == EXT2_ET_DIR_NO_SPACE) { |
|
retval = ext2fs_expand_dir(fs, parent_ino); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while expanding directory")); |
|
return retval; |
|
} |
|
retval = ext2fs_mkdir(fs, parent_ino, 0, name); |
|
} |
|
if (retval) |
|
com_err("ext2fs_mkdir", retval, |
|
_("while creating directory \"%s\""), name); |
|
return retval; |
|
} |
|
|
|
#if !defined HAVE_PREAD64 && !defined HAVE_PREAD |
|
static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset) |
|
{ |
|
if (lseek(fd, offset, SEEK_SET) < 0) |
|
return 0; |
|
|
|
return read(fd, buf, count); |
|
} |
|
#endif /* !defined HAVE_PREAD64 && !defined HAVE_PREAD */ |
|
|
|
static errcode_t copy_file_range(ext2_filsys fs, int fd, ext2_file_t e2_file, |
|
off_t start, off_t end, char *buf, |
|
char *zerobuf) |
|
{ |
|
off_t off, bpos; |
|
ssize_t got, blen; |
|
unsigned int written; |
|
char *ptr; |
|
errcode_t err = 0; |
|
|
|
for (off = start; off < end; off += COPY_FILE_BUFLEN) { |
|
#ifdef HAVE_PREAD64 |
|
got = pread64(fd, buf, COPY_FILE_BUFLEN, off); |
|
#elif HAVE_PREAD |
|
got = pread(fd, buf, COPY_FILE_BUFLEN, off); |
|
#else |
|
got = my_pread(fd, buf, COPY_FILE_BUFLEN, off); |
|
#endif |
|
if (got < 0) { |
|
err = errno; |
|
goto fail; |
|
} |
|
for (bpos = 0, ptr = buf; bpos < got; bpos += fs->blocksize) { |
|
blen = fs->blocksize; |
|
if (blen > got - bpos) |
|
blen = got - bpos; |
|
if (memcmp(ptr, zerobuf, blen) == 0) { |
|
ptr += blen; |
|
continue; |
|
} |
|
err = ext2fs_file_lseek(e2_file, off + bpos, |
|
EXT2_SEEK_SET, NULL); |
|
if (err) |
|
goto fail; |
|
while (blen > 0) { |
|
err = ext2fs_file_write(e2_file, ptr, blen, |
|
&written); |
|
if (err) |
|
goto fail; |
|
if (written == 0) { |
|
err = EIO; |
|
goto fail; |
|
} |
|
blen -= written; |
|
ptr += written; |
|
} |
|
} |
|
} |
|
fail: |
|
return err; |
|
} |
|
|
|
#if defined(SEEK_DATA) && defined(SEEK_HOLE) |
|
static errcode_t try_lseek_copy(ext2_filsys fs, int fd, struct stat *statbuf, |
|
ext2_file_t e2_file, char *buf, char *zerobuf) |
|
{ |
|
off_t data = 0, hole; |
|
off_t data_blk, hole_blk; |
|
errcode_t err = 0; |
|
|
|
/* Try to use SEEK_DATA and SEEK_HOLE */ |
|
while (data < statbuf->st_size) { |
|
data = lseek(fd, data, SEEK_DATA); |
|
if (data < 0) { |
|
if (errno == ENXIO) |
|
break; |
|
return EXT2_ET_UNIMPLEMENTED; |
|
} |
|
hole = lseek(fd, data, SEEK_HOLE); |
|
if (hole < 0) |
|
return EXT2_ET_UNIMPLEMENTED; |
|
|
|
data_blk = data & ~(fs->blocksize - 1); |
|
hole_blk = (hole + (fs->blocksize - 1)) & ~(fs->blocksize - 1); |
|
err = copy_file_range(fs, fd, e2_file, data_blk, hole_blk, buf, |
|
zerobuf); |
|
if (err) |
|
return err; |
|
|
|
data = hole; |
|
} |
|
|
|
return err; |
|
} |
|
#endif /* SEEK_DATA and SEEK_HOLE */ |
|
|
|
#if defined(FS_IOC_FIEMAP) |
|
static errcode_t try_fiemap_copy(ext2_filsys fs, int fd, ext2_file_t e2_file, |
|
char *buf, char *zerobuf) |
|
{ |
|
#define EXTENT_MAX_COUNT 512 |
|
struct fiemap *fiemap_buf; |
|
struct fiemap_extent *ext_buf, *ext; |
|
int ext_buf_size, fie_buf_size; |
|
off_t pos = 0; |
|
unsigned int i; |
|
errcode_t err; |
|
|
|
ext_buf_size = EXTENT_MAX_COUNT * sizeof(struct fiemap_extent); |
|
fie_buf_size = sizeof(struct fiemap) + ext_buf_size; |
|
|
|
err = ext2fs_get_memzero(fie_buf_size, &fiemap_buf); |
|
if (err) |
|
return err; |
|
|
|
ext_buf = fiemap_buf->fm_extents; |
|
memset(fiemap_buf, 0, fie_buf_size); |
|
fiemap_buf->fm_length = FIEMAP_MAX_OFFSET; |
|
fiemap_buf->fm_flags |= FIEMAP_FLAG_SYNC; |
|
fiemap_buf->fm_extent_count = EXTENT_MAX_COUNT; |
|
|
|
do { |
|
fiemap_buf->fm_start = pos; |
|
memset(ext_buf, 0, ext_buf_size); |
|
err = ioctl(fd, FS_IOC_FIEMAP, fiemap_buf); |
|
if (err < 0 && (errno == EOPNOTSUPP || errno == ENOTTY)) { |
|
err = EXT2_ET_UNIMPLEMENTED; |
|
goto out; |
|
} else if (err < 0 || fiemap_buf->fm_mapped_extents == 0) { |
|
err = errno; |
|
goto out; |
|
} |
|
for (i = 0, ext = ext_buf; i < fiemap_buf->fm_mapped_extents; |
|
i++, ext++) { |
|
err = copy_file_range(fs, fd, e2_file, ext->fe_logical, |
|
ext->fe_logical + ext->fe_length, |
|
buf, zerobuf); |
|
if (err) |
|
goto out; |
|
} |
|
|
|
ext--; |
|
/* Record file's logical offset this time */ |
|
pos = ext->fe_logical + ext->fe_length; |
|
/* |
|
* If fm_extents array has been filled and |
|
* there are extents left, continue to cycle. |
|
*/ |
|
} while (fiemap_buf->fm_mapped_extents == EXTENT_MAX_COUNT && |
|
!(ext->fe_flags & FIEMAP_EXTENT_LAST)); |
|
out: |
|
ext2fs_free_mem(&fiemap_buf); |
|
return err; |
|
} |
|
#endif /* FS_IOC_FIEMAP */ |
|
|
|
static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf, |
|
ext2_ino_t ino) |
|
{ |
|
ext2_file_t e2_file; |
|
char *buf = NULL, *zerobuf = NULL; |
|
errcode_t err, close_err; |
|
|
|
err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &e2_file); |
|
if (err) |
|
return err; |
|
|
|
err = ext2fs_get_mem(COPY_FILE_BUFLEN, &buf); |
|
if (err) |
|
goto out; |
|
|
|
err = ext2fs_get_memzero(fs->blocksize, &zerobuf); |
|
if (err) |
|
goto out; |
|
|
|
#if defined(SEEK_DATA) && defined(SEEK_HOLE) |
|
err = try_lseek_copy(fs, fd, statbuf, e2_file, buf, zerobuf); |
|
if (err != EXT2_ET_UNIMPLEMENTED) |
|
goto out; |
|
#endif |
|
|
|
#if defined(FS_IOC_FIEMAP) |
|
err = try_fiemap_copy(fs, fd, e2_file, buf, zerobuf); |
|
if (err != EXT2_ET_UNIMPLEMENTED) |
|
goto out; |
|
#endif |
|
|
|
err = copy_file_range(fs, fd, e2_file, 0, statbuf->st_size, buf, |
|
zerobuf); |
|
out: |
|
ext2fs_free_mem(&zerobuf); |
|
ext2fs_free_mem(&buf); |
|
close_err = ext2fs_file_close(e2_file); |
|
if (err == 0) |
|
err = close_err; |
|
return err; |
|
} |
|
|
|
static int is_hardlink(struct hdlinks_s *hdlinks, dev_t dev, ino_t ino) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < hdlinks->count; i++) { |
|
if (hdlinks->hdl[i].src_dev == dev && |
|
hdlinks->hdl[i].src_ino == ino) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
/* Copy the native file to the fs */ |
|
errcode_t do_write_internal(ext2_filsys fs, ext2_ino_t cwd, const char *src, |
|
const char *dest, ext2_ino_t root) |
|
{ |
|
int fd; |
|
struct stat statbuf; |
|
ext2_ino_t newfile; |
|
errcode_t retval; |
|
struct ext2_inode inode; |
|
|
|
fd = ext2fs_open_file(src, O_RDONLY, 0); |
|
if (fd < 0) { |
|
retval = errno; |
|
com_err(__func__, retval, _("while opening \"%s\" to copy"), |
|
src); |
|
return retval; |
|
} |
|
if (fstat(fd, &statbuf) < 0) { |
|
retval = errno; |
|
goto out; |
|
} |
|
|
|
retval = ext2fs_namei(fs, root, cwd, dest, &newfile); |
|
if (retval == 0) { |
|
retval = EXT2_ET_FILE_EXISTS; |
|
goto out; |
|
} |
|
|
|
retval = ext2fs_new_inode(fs, cwd, 010755, 0, &newfile); |
|
if (retval) |
|
goto out; |
|
#ifdef DEBUGFS |
|
printf("Allocated inode: %u\n", newfile); |
|
#endif |
|
retval = ext2fs_link(fs, cwd, dest, newfile, |
|
EXT2_FT_REG_FILE); |
|
if (retval == EXT2_ET_DIR_NO_SPACE) { |
|
retval = ext2fs_expand_dir(fs, cwd); |
|
if (retval) |
|
goto out; |
|
retval = ext2fs_link(fs, cwd, dest, newfile, |
|
EXT2_FT_REG_FILE); |
|
} |
|
if (retval) |
|
goto out; |
|
if (ext2fs_test_inode_bitmap2(fs->inode_map, newfile)) |
|
com_err(__func__, 0, "Warning: inode already set"); |
|
ext2fs_inode_alloc_stats2(fs, newfile, +1, 0); |
|
memset(&inode, 0, sizeof(inode)); |
|
inode.i_mode = (statbuf.st_mode & ~LINUX_S_IFMT) | LINUX_S_IFREG; |
|
inode.i_atime = inode.i_ctime = inode.i_mtime = |
|
fs->now ? fs->now : time(0); |
|
inode.i_links_count = 1; |
|
retval = ext2fs_inode_size_set(fs, &inode, statbuf.st_size); |
|
if (retval) |
|
goto out; |
|
if (ext2fs_has_feature_inline_data(fs->super)) { |
|
inode.i_flags |= EXT4_INLINE_DATA_FL; |
|
} else if (ext2fs_has_feature_extents(fs->super)) { |
|
ext2_extent_handle_t handle; |
|
|
|
inode.i_flags &= ~EXT4_EXTENTS_FL; |
|
retval = ext2fs_extent_open2(fs, newfile, &inode, &handle); |
|
if (retval) |
|
goto out; |
|
ext2fs_extent_free(handle); |
|
} |
|
|
|
retval = ext2fs_write_new_inode(fs, newfile, &inode); |
|
if (retval) |
|
goto out; |
|
if (inode.i_flags & EXT4_INLINE_DATA_FL) { |
|
retval = ext2fs_inline_data_init(fs, newfile); |
|
if (retval) |
|
goto out; |
|
} |
|
if (LINUX_S_ISREG(inode.i_mode)) { |
|
retval = copy_file(fs, fd, &statbuf, newfile); |
|
if (retval) |
|
goto out; |
|
} |
|
out: |
|
close(fd); |
|
return retval; |
|
} |
|
|
|
struct file_info { |
|
char *path; |
|
size_t path_len; |
|
size_t path_max_len; |
|
}; |
|
|
|
static errcode_t path_append(struct file_info *target, const char *file) |
|
{ |
|
if (strlen(file) + target->path_len + 1 > target->path_max_len) { |
|
target->path_max_len *= 2; |
|
target->path = realloc(target->path, target->path_max_len); |
|
if (!target->path) |
|
return EXT2_ET_NO_MEMORY; |
|
} |
|
target->path_len += sprintf(target->path + target->path_len, "/%s", |
|
file); |
|
return 0; |
|
} |
|
|
|
/* Copy files from source_dir to fs */ |
|
static errcode_t __populate_fs(ext2_filsys fs, ext2_ino_t parent_ino, |
|
const char *source_dir, ext2_ino_t root, |
|
struct hdlinks_s *hdlinks, |
|
struct file_info *target, |
|
struct fs_ops_callbacks *fs_callbacks) |
|
{ |
|
const char *name; |
|
DIR *dh; |
|
struct dirent *dent; |
|
struct stat st; |
|
char *ln_target = NULL; |
|
unsigned int save_inode; |
|
ext2_ino_t ino; |
|
errcode_t retval = 0; |
|
int read_cnt; |
|
int hdlink; |
|
size_t cur_dir_path_len; |
|
|
|
if (chdir(source_dir) < 0) { |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while changing working directory to \"%s\""), |
|
source_dir); |
|
return retval; |
|
} |
|
|
|
if (!(dh = opendir("."))) { |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while opening directory \"%s\""), source_dir); |
|
return retval; |
|
} |
|
|
|
while ((dent = readdir(dh))) { |
|
if ((!strcmp(dent->d_name, ".")) || |
|
(!strcmp(dent->d_name, ".."))) |
|
continue; |
|
if (lstat(dent->d_name, &st)) { |
|
retval = errno; |
|
com_err(__func__, retval, _("while lstat \"%s\""), |
|
dent->d_name); |
|
goto out; |
|
} |
|
name = dent->d_name; |
|
|
|
/* Check for hardlinks */ |
|
save_inode = 0; |
|
if (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) && |
|
st.st_nlink > 1) { |
|
hdlink = is_hardlink(hdlinks, st.st_dev, st.st_ino); |
|
if (hdlink >= 0) { |
|
retval = add_link(fs, parent_ino, |
|
hdlinks->hdl[hdlink].dst_ino, |
|
name); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
"while linking %s", name); |
|
goto out; |
|
} |
|
continue; |
|
} else |
|
save_inode = 1; |
|
} |
|
|
|
cur_dir_path_len = target->path_len; |
|
retval = path_append(target, name); |
|
if (retval) |
|
return retval; |
|
|
|
if (fs_callbacks && fs_callbacks->create_new_inode) { |
|
retval = fs_callbacks->create_new_inode(fs, |
|
target->path, name, parent_ino, root, |
|
st.st_mode & S_IFMT); |
|
if (retval) |
|
goto out; |
|
} |
|
|
|
switch(st.st_mode & S_IFMT) { |
|
case S_IFCHR: |
|
case S_IFBLK: |
|
case S_IFIFO: |
|
#ifndef _WIN32 |
|
case S_IFSOCK: |
|
retval = do_mknod_internal(fs, parent_ino, name, &st); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while creating special file " |
|
"\"%s\""), name); |
|
goto out; |
|
} |
|
break; |
|
case S_IFLNK: |
|
ln_target = malloc(st.st_size + 1); |
|
if (ln_target == NULL) { |
|
com_err(__func__, retval, |
|
_("malloc failed")); |
|
goto out; |
|
} |
|
read_cnt = readlink(name, ln_target, |
|
st.st_size + 1); |
|
if (read_cnt == -1) { |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while trying to read link \"%s\""), |
|
name); |
|
free(ln_target); |
|
goto out; |
|
} |
|
if (read_cnt > st.st_size) { |
|
com_err(__func__, retval, |
|
_("symlink increased in size " |
|
"between lstat() and readlink()")); |
|
free(ln_target); |
|
goto out; |
|
} |
|
ln_target[read_cnt] = '\0'; |
|
retval = do_symlink_internal(fs, parent_ino, name, |
|
ln_target, root); |
|
free(ln_target); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while writing symlink\"%s\""), |
|
name); |
|
goto out; |
|
} |
|
break; |
|
#endif |
|
case S_IFREG: |
|
retval = do_write_internal(fs, parent_ino, name, name, |
|
root); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while writing file \"%s\""), name); |
|
goto out; |
|
} |
|
break; |
|
case S_IFDIR: |
|
/* Don't choke on /lost+found */ |
|
if (parent_ino == EXT2_ROOT_INO && |
|
strcmp(name, "lost+found") == 0) |
|
goto find_lnf; |
|
retval = do_mkdir_internal(fs, parent_ino, name, |
|
root); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while making dir \"%s\""), name); |
|
goto out; |
|
} |
|
find_lnf: |
|
retval = ext2fs_namei(fs, root, parent_ino, |
|
name, &ino); |
|
if (retval) { |
|
com_err(name, retval, 0); |
|
goto out; |
|
} |
|
/* Populate the dir recursively*/ |
|
retval = __populate_fs(fs, ino, name, root, hdlinks, |
|
target, fs_callbacks); |
|
if (retval) |
|
goto out; |
|
if (chdir("..")) { |
|
retval = errno; |
|
com_err(__func__, retval, |
|
_("while changing directory")); |
|
goto out; |
|
} |
|
break; |
|
default: |
|
com_err(__func__, 0, |
|
_("ignoring entry \"%s\""), name); |
|
} |
|
|
|
retval = ext2fs_namei(fs, root, parent_ino, name, &ino); |
|
if (retval) { |
|
com_err(name, retval, _("while looking up \"%s\""), |
|
name); |
|
goto out; |
|
} |
|
|
|
retval = set_inode_extra(fs, ino, &st); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while setting inode for \"%s\""), name); |
|
goto out; |
|
} |
|
|
|
retval = set_inode_xattr(fs, ino, name); |
|
if (retval) { |
|
com_err(__func__, retval, |
|
_("while setting xattrs for \"%s\""), name); |
|
goto out; |
|
} |
|
|
|
if (fs_callbacks && fs_callbacks->end_create_new_inode) { |
|
retval = fs_callbacks->end_create_new_inode(fs, |
|
target->path, name, parent_ino, root, |
|
st.st_mode & S_IFMT); |
|
if (retval) |
|
goto out; |
|
} |
|
|
|
/* Save the hardlink ino */ |
|
if (save_inode) { |
|
/* |
|
* Check whether need more memory, and we don't need |
|
* free() since the lifespan will be over after the fs |
|
* populated. |
|
*/ |
|
if (hdlinks->count == hdlinks->size) { |
|
void *p = realloc(hdlinks->hdl, |
|
(hdlinks->size + HDLINK_CNT) * |
|
sizeof(struct hdlink_s)); |
|
if (p == NULL) { |
|
retval = EXT2_ET_NO_MEMORY; |
|
com_err(name, retval, |
|
_("while saving inode data")); |
|
goto out; |
|
} |
|
hdlinks->hdl = p; |
|
hdlinks->size += HDLINK_CNT; |
|
} |
|
hdlinks->hdl[hdlinks->count].src_dev = st.st_dev; |
|
hdlinks->hdl[hdlinks->count].src_ino = st.st_ino; |
|
hdlinks->hdl[hdlinks->count].dst_ino = ino; |
|
hdlinks->count++; |
|
} |
|
target->path_len = cur_dir_path_len; |
|
target->path[target->path_len] = 0; |
|
} |
|
|
|
out: |
|
closedir(dh); |
|
return retval; |
|
} |
|
|
|
errcode_t populate_fs2(ext2_filsys fs, ext2_ino_t parent_ino, |
|
const char *source_dir, ext2_ino_t root, |
|
struct fs_ops_callbacks *fs_callbacks) |
|
{ |
|
struct file_info file_info; |
|
struct hdlinks_s hdlinks; |
|
errcode_t retval; |
|
|
|
if (!(fs->flags & EXT2_FLAG_RW)) { |
|
com_err(__func__, 0, "Filesystem opened readonly"); |
|
return EROFS; |
|
} |
|
|
|
hdlinks.count = 0; |
|
hdlinks.size = HDLINK_CNT; |
|
hdlinks.hdl = realloc(NULL, hdlinks.size * sizeof(struct hdlink_s)); |
|
if (hdlinks.hdl == NULL) { |
|
retval = errno; |
|
com_err(__func__, retval, _("while allocating memory")); |
|
return retval; |
|
} |
|
|
|
file_info.path_len = 0; |
|
file_info.path_max_len = 255; |
|
file_info.path = calloc(file_info.path_max_len, 1); |
|
|
|
retval = __populate_fs(fs, parent_ino, source_dir, root, &hdlinks, |
|
&file_info, fs_callbacks); |
|
|
|
free(file_info.path); |
|
free(hdlinks.hdl); |
|
return retval; |
|
} |
|
|
|
errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino, |
|
const char *source_dir, ext2_ino_t root) |
|
{ |
|
return populate_fs2(fs, parent_ino, source_dir, root, NULL); |
|
}
|
|
|