| /* |
| * 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/types.h> |
| #include <unistd.h> |
| #include <limits.h> /* for PATH_MAX */ |
| #ifdef HAVE_ATTR_XATTR_H |
| #include <attr/xattr.h> |
| #endif |
| #include <sys/ioctl.h> |
| #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 */ |
| |
| /* 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; |
| case S_IFSOCK: |
| mode = LINUX_S_IFSOCK; |
| filetype = EXT2_FT_SOCK; |
| break; |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| static errcode_t try_lseek_copy(ext2_filsys fs, int fd, struct stat *statbuf, |
| ext2_file_t e2_file, char *buf, char *zerobuf) |
| { |
| #if defined(SEEK_DATA) && defined(SEEK_HOLE) |
| off_t data = 0, hole; |
| off_t data_blk, hole_blk; |
| errcode_t err; |
| |
| /* 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; |
| #else |
| return EXT2_ET_UNIMPLEMENTED; |
| #endif /* SEEK_DATA and SEEK_HOLE */ |
| } |
| |
| static errcode_t try_fiemap_copy(ext2_filsys fs, int fd, ext2_file_t e2_file, |
| char *buf, char *zerobuf) |
| { |
| #if defined(FS_IOC_FIEMAP) |
| #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; |
| #else |
| return EXT2_ET_UNIMPLEMENTED; |
| #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; |
| |
| err = try_lseek_copy(fs, fd, statbuf, e2_file, buf, zerobuf); |
| if (err != EXT2_ET_UNIMPLEMENTED) |
| goto out; |
| |
| err = try_fiemap_copy(fs, fd, e2_file, buf, zerobuf); |
| if (err != EXT2_ET_UNIMPLEMENTED) |
| goto out; |
| |
| 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; |
| } |
| |
| /* 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) |
| { |
| 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; |
| |
| 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; |
| } |
| |
| switch(st.st_mode & S_IFMT) { |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFIFO: |
| 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; |
| 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); |
| 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; |
| } |
| |
| /* 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++; |
| } |
| } |
| |
| out: |
| closedir(dh); |
| return retval; |
| } |
| |
| errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino, |
| const char *source_dir, ext2_ino_t root) |
| { |
| 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; |
| } |
| |
| retval = __populate_fs(fs, parent_ino, source_dir, root, &hdlinks); |
| |
| free(hdlinks.hdl); |
| return retval; |
| } |