#include #include #include "create_inode.h" #if __STDC_VERSION__ < 199901L # if __GNUC__ >= 2 # define __func__ __FUNCTION__ # else # define __func__ "" # endif #endif /* 64KiB is the minimium blksize to best minimize system call overhead. */ #ifndef IO_BUFSIZE #define IO_BUFSIZE 64*1024 #endif /* Block size for `st_blocks' */ #ifndef S_BLKSIZE #define S_BLKSIZE 512 #endif /* For saving the hard links */ int hdlink_cnt = HDLINK_CNT; /* 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, inode.i_flags); 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, inode.i_flags); } 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; } /* Fill the uid, gid, mode and time for the inode */ static void fill_inode(struct ext2_inode *inode, struct stat *st) { if (st != NULL) { 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; } } /* Set the uid, gid, mode and time for the inode */ static errcode_t set_inode_extra(ext2_filsys fs, ext2_ino_t cwd, 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; } fill_inode(&inode, st); retval = ext2fs_write_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, "while writing inode %u", ino); return retval; } } /* Make a special file which is block, character and fifo */ 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 major, minor, 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; } if (!(fs->flags & EXT2_FLAG_RW)) { com_err(__func__, 0, "Filesystem opened read/only"); return -1; } retval = ext2fs_new_inode(fs, cwd, 010755, 0, &ino); if (retval) { com_err(__func__, retval, 0); 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, 0); return -1; } 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); major = major(st->st_rdev); minor = minor(st->st_rdev); if ((major < 256) && (minor < 256)) { inode.i_block[0] = major * 256 + minor; inode.i_block[1] = 0; } else { inode.i_block[0] = 0; inode.i_block[1] = (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); } inode.i_links_count = 1; retval = ext2fs_write_new_inode(fs, ino, &inode); if (retval) com_err(__func__, retval, "while creating 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; struct ext2_inode inode; struct stat st; 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; try_again: 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; } goto try_again; } if (retval) { com_err("ext2fs_symlink", retval, 0); return retval; } } /* Make a directory in the fs */ errcode_t do_mkdir_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name, struct stat *st, ext2_ino_t root) { char *cp; ext2_ino_t parent_ino, ino; errcode_t retval; struct ext2_inode inode; 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; try_again: 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; } goto try_again; } if (retval) { com_err("ext2fs_mkdir", retval, 0); return retval; } } static errcode_t copy_file(ext2_filsys fs, int fd, ext2_ino_t newfile, int bufsize, int make_holes) { ext2_file_t e2_file; errcode_t retval; int got; unsigned int written; char *buf; char *ptr; char *zero_buf; int cmp; retval = ext2fs_file_open(fs, newfile, EXT2_FILE_WRITE, &e2_file); if (retval) return retval; retval = ext2fs_get_mem(bufsize, &buf); if (retval) { com_err("copy_file", retval, "can't allocate buffer\n"); return retval; } /* This is used for checking whether the whole block is zero */ retval = ext2fs_get_memzero(bufsize, &zero_buf); if (retval) { com_err("copy_file", retval, "can't allocate buffer\n"); ext2fs_free_mem(&buf); return retval; } while (1) { got = read(fd, buf, bufsize); if (got == 0) break; if (got < 0) { retval = errno; goto fail; } ptr = buf; /* Sparse copy */ if (make_holes) { /* Check whether all is zero */ cmp = memcmp(ptr, zero_buf, got); if (cmp == 0) { /* The whole block is zero, make a hole */ retval = ext2fs_file_lseek(e2_file, got, EXT2_SEEK_CUR, NULL); if (retval) goto fail; got = 0; } } /* Normal copy */ while (got > 0) { retval = ext2fs_file_write(e2_file, ptr, got, &written); if (retval) goto fail; got -= written; ptr += written; } } ext2fs_free_mem(&buf); ext2fs_free_mem(&zero_buf); retval = ext2fs_file_close(e2_file); return retval; fail: ext2fs_free_mem(&buf); ext2fs_free_mem(&zero_buf); (void) ext2fs_file_close(e2_file); return retval; } static int is_hardlink(ext2_ino_t ino) { int i; for(i = 0; i < hdlinks.count; i++) { if(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; int bufsize = IO_BUFSIZE; int make_holes = 0; fd = open(src, O_RDONLY); if (fd < 0) { com_err(src, errno, 0); return errno; } if (fstat(fd, &statbuf) < 0) { com_err(src, errno, 0); close(fd); return errno; } retval = ext2fs_namei(fs, root, cwd, dest, &newfile); if (retval == 0) { close(fd); return EXT2_ET_FILE_EXISTS; } retval = ext2fs_new_inode(fs, cwd, 010755, 0, &newfile); if (retval) { com_err(__func__, retval, 0); close(fd); return retval; } #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) { com_err(__func__, retval, "while expanding directory"); close(fd); return retval; } retval = ext2fs_link(fs, cwd, dest, newfile, EXT2_FT_REG_FILE); } if (retval) { com_err(dest, retval, 0); close(fd); return errno; } 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; inode.i_size = statbuf.st_size; if (EXT2_HAS_INCOMPAT_FEATURE(fs->super, EXT4_FEATURE_INCOMPAT_INLINE_DATA)) { inode.i_flags |= EXT4_INLINE_DATA_FL; } else if (fs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_EXTENTS) { int i; struct ext3_extent_header *eh; eh = (struct ext3_extent_header *) &inode.i_block[0]; eh->eh_depth = 0; eh->eh_entries = 0; eh->eh_magic = ext2fs_cpu_to_le16(EXT3_EXT_MAGIC); i = (sizeof(inode.i_block) - sizeof(*eh)) / sizeof(struct ext3_extent); eh->eh_max = ext2fs_cpu_to_le16(i); inode.i_flags |= EXT4_EXTENTS_FL; } retval = ext2fs_write_new_inode(fs, newfile, &inode); if (retval) { com_err(__func__, retval, "while creating inode %u", newfile); close(fd); return retval; } if (inode.i_flags & EXT4_INLINE_DATA_FL) { retval = ext2fs_inline_data_init(fs, newfile); if (retval) { com_err("copy_file", retval, 0); close(fd); return retval; } } if (LINUX_S_ISREG(inode.i_mode)) { if (statbuf.st_blocks < statbuf.st_size / S_BLKSIZE) { make_holes = 1; /* * Use I/O blocksize as buffer size when * copying sparse files. */ bufsize = statbuf.st_blksize; } retval = copy_file(fs, fd, newfile, bufsize, make_holes); if (retval) com_err("copy_file", retval, 0); } close(fd); return retval; } /* Copy files from source_dir to fs */ errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino, const char *source_dir, ext2_ino_t root) { const char *name; DIR *dh; struct dirent *dent; struct stat st; char ln_target[PATH_MAX]; unsigned int save_inode; ext2_ino_t ino; errcode_t retval; int read_cnt; int hdlink; if (chdir(source_dir) < 0) { com_err(__func__, errno, _("while changing working directory to \"%s\""), source_dir); return errno; } if (!(dh = opendir("."))) { com_err(__func__, errno, _("while opening directory \"%s\""), source_dir); return errno; } while ((dent = readdir(dh))) { if ((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, ".."))) continue; lstat(dent->d_name, &st); 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(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); return retval; } continue; } else save_inode = 1; } switch(st.st_mode & S_IFMT) { case S_IFCHR: case S_IFBLK: case S_IFIFO: retval = do_mknod_internal(fs, parent_ino, name, &st); if (retval) { com_err(__func__, retval, _("while creating special file " "\"%s\""), name); return retval; } break; case S_IFSOCK: /* FIXME: there is no make socket function atm. */ com_err(__func__, 0, _("ignoring socket file \"%s\""), name); continue; case S_IFLNK: read_cnt = readlink(name, ln_target, sizeof(ln_target)); if (read_cnt == -1) { com_err(__func__, errno, _("while trying to readlink \"%s\""), name); return errno; } ln_target[read_cnt] = '\0'; retval = do_symlink_internal(fs, parent_ino, name, ln_target, root); if (retval) { com_err(__func__, retval, _("while writing symlink\"%s\""), name); return retval; } 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); return retval; } break; case S_IFDIR: retval = do_mkdir_internal(fs, parent_ino, name, &st, root); if (retval) { com_err(__func__, retval, _("while making dir \"%s\""), name); return retval; } retval = ext2fs_namei(fs, root, parent_ino, name, &ino); if (retval) { com_err(name, retval, 0); return retval; } /* Populate the dir recursively*/ retval = populate_fs(fs, ino, name, root); if (retval) { com_err(__func__, retval, _("while adding dir \"%s\""), name); return retval; } if (chdir("..")) { com_err(__func__, errno, _("during cd ..")); return errno; } 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, 0); return retval; } retval = set_inode_extra(fs, parent_ino, ino, &st); if (retval) { com_err(__func__, retval, _("while setting inode for \"%s\""), name); return retval; } /* 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 == hdlink_cnt) { if ((hdlinks.hdl = realloc (hdlinks.hdl, (hdlink_cnt + HDLINK_CNT) * sizeof (struct hdlink_s))) == NULL) { com_err(name, errno, "Not enough memory"); return errno; } hdlink_cnt += HDLINK_CNT; } hdlinks.hdl[hdlinks.count].src_ino = st.st_ino; hdlinks.hdl[hdlinks.count].dst_ino = ino; hdlinks.count++; } } closedir(dh); return retval; }