// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include #include #include #if defined(HAVE_ZLIB) #include #endif #include "erofs/print.h" #include "erofs/cache.h" #include "erofs/diskbuf.h" #include "erofs/inode.h" #include "erofs/list.h" #include "erofs/tar.h" #include "erofs/xattr.h" #include "erofs/blobchunk.h" #include "erofs/rebuild.h" /* This file is a tape/volume header. Ignore it on extraction. */ #define GNUTYPE_VOLHDR 'V' struct tar_header { char name[100]; /* 0-99 */ char mode[8]; /* 100-107 */ char uid[8]; /* 108-115 */ char gid[8]; /* 116-123 */ char size[12]; /* 124-135 */ char mtime[12]; /* 136-147 */ char chksum[8]; /* 148-155 */ char typeflag; /* 156-156 */ char linkname[100]; /* 157-256 */ char magic[6]; /* 257-262 */ char version[2]; /* 263-264 */ char uname[32]; /* 265-296 */ char gname[32]; /* 297-328 */ char devmajor[8]; /* 329-336 */ char devminor[8]; /* 337-344 */ char prefix[155]; /* 345-499 */ char padding[12]; /* 500-512 (pad to exactly the 512 byte) */ }; void erofs_iostream_close(struct erofs_iostream *ios) { free(ios->buffer); if (ios->decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) gzclose(ios->handler); #endif return; } else if (ios->decoder == EROFS_IOS_DECODER_LIBLZMA) { #if defined(HAVE_LIBLZMA) lzma_end(&ios->lzma->strm); close(ios->lzma->fd); free(ios->lzma); #endif return; } close(ios->vf.fd); } int erofs_iostream_open(struct erofs_iostream *ios, int fd, int decoder) { s64 fsz; ios->feof = false; ios->tail = ios->head = 0; ios->decoder = decoder; ios->dumpfd = -1; if (decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) ios->handler = gzdopen(fd, "r"); if (!ios->handler) return -ENOMEM; ios->sz = fsz = 0; ios->bufsize = 32768; #else return -EOPNOTSUPP; #endif } else if (decoder == EROFS_IOS_DECODER_LIBLZMA) { #ifdef HAVE_LIBLZMA lzma_ret ret; ios->lzma = malloc(sizeof(*ios->lzma)); if (!ios->lzma) return -ENOMEM; ios->lzma->fd = fd; ios->lzma->strm = (lzma_stream)LZMA_STREAM_INIT; ret = lzma_auto_decoder(&ios->lzma->strm, UINT64_MAX, LZMA_CONCATENATED); if (ret != LZMA_OK) return -EFAULT; ios->sz = fsz = 0; ios->bufsize = 32768; #else return -EOPNOTSUPP; #endif } else { ios->vf.fd = fd; fsz = lseek(fd, 0, SEEK_END); if (fsz <= 0) { ios->feof = !fsz; ios->sz = 0; } else { ios->sz = fsz; if (lseek(fd, 0, SEEK_SET)) return -EIO; #ifdef HAVE_POSIX_FADVISE if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) erofs_warn("failed to fadvise: %s, ignored.", erofs_strerror(-errno)); #endif } ios->bufsize = 16384; } do { ios->buffer = malloc(ios->bufsize); if (ios->buffer) break; ios->bufsize >>= 1; } while (ios->bufsize >= 1024); if (!ios->buffer) return -ENOMEM; return 0; } int erofs_iostream_read(struct erofs_iostream *ios, void **buf, u64 bytes) { unsigned int rabytes = ios->tail - ios->head; int ret; if (rabytes >= bytes) { *buf = ios->buffer + ios->head; ios->head += bytes; return bytes; } if (ios->head) { memmove(ios->buffer, ios->buffer + ios->head, rabytes); ios->head = 0; ios->tail = rabytes; } if (!ios->feof) { if (ios->decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) ret = gzread(ios->handler, ios->buffer + rabytes, ios->bufsize - rabytes); if (!ret) { int errnum; const char *errstr; errstr = gzerror(ios->handler, &errnum); if (errnum != Z_STREAM_END) { erofs_err("failed to gzread: %s", errstr); return -EIO; } ios->feof = true; } ios->tail += ret; #else return -EOPNOTSUPP; #endif } else if (ios->decoder == EROFS_IOS_DECODER_LIBLZMA) { #ifdef HAVE_LIBLZMA struct erofs_iostream_liblzma *lzma = ios->lzma; lzma_action action = LZMA_RUN; lzma_ret ret2; if (!lzma->strm.avail_in) { lzma->strm.next_in = lzma->inbuf; ret = read(lzma->fd, lzma->inbuf, sizeof(lzma->inbuf)); if (ret < 0) return -errno; lzma->strm.avail_in = ret; if (ret < sizeof(lzma->inbuf)) action = LZMA_FINISH; } lzma->strm.next_out = (u8 *)ios->buffer + rabytes; lzma->strm.avail_out = ios->bufsize - rabytes; ret2 = lzma_code(&lzma->strm, action); if (ret2 != LZMA_OK) { if (ret2 == LZMA_STREAM_END) ios->feof = true; else return -EIO; } ret = ios->bufsize - rabytes - lzma->strm.avail_out; ios->tail += ret; #else return -EOPNOTSUPP; #endif } else { ret = erofs_io_read(&ios->vf, ios->buffer + rabytes, ios->bufsize - rabytes); if (ret < 0) return ret; ios->tail += ret; if (ret < ios->bufsize - rabytes) ios->feof = true; } if (__erofs_unlikely(ios->dumpfd >= 0)) if (write(ios->dumpfd, ios->buffer + rabytes, ret) < ret) erofs_err("failed to dump %d bytes of the raw stream: %s", ret, erofs_strerror(-errno)); } *buf = ios->buffer; ret = min_t(int, ios->tail, min_t(u64, bytes, INT_MAX)); ios->head = ret; return ret; } int erofs_iostream_bread(struct erofs_iostream *ios, void *buf, u64 bytes) { u64 rem = bytes; void *src; int ret; do { ret = erofs_iostream_read(ios, &src, rem); if (ret < 0) return ret; memcpy(buf, src, ret); rem -= ret; } while (rem && ret); return bytes - rem; } int erofs_iostream_lskip(struct erofs_iostream *ios, u64 sz) { unsigned int rabytes = ios->tail - ios->head; int ret; void *dummy; if (rabytes >= sz) { ios->head += sz; return 0; } sz -= rabytes; ios->head = ios->tail = 0; if (ios->feof) return sz; if (ios->sz && __erofs_likely(ios->dumpfd < 0)) { s64 cur = erofs_io_lseek(&ios->vf, sz, SEEK_CUR); if (cur > ios->sz) return cur - ios->sz; return 0; } do { ret = erofs_iostream_read(ios, &dummy, sz); if (ret < 0) return ret; sz -= ret; } while (!(ios->feof || !ret || !sz)); return sz; } static long long tarerofs_otoi(const char *ptr, int len) { char inp[32]; char *endp = inp; long long val; memcpy(inp, ptr, len); inp[len] = '\0'; errno = 0; val = strtol(ptr, &endp, 8); if ((!val && endp == inp) | (*endp && *endp != ' ')) errno = EINVAL; return val; } static long long tarerofs_parsenum(const char *ptr, int len) { /* * For fields containing numbers or timestamps that are out of range * for the basic format, the GNU format uses a base-256 representation * instead of an ASCII octal number. */ if (*(char *)ptr == '\200') { long long res = 0; while (--len) res = (res << 8) + (u8)*(++ptr); return res; } return tarerofs_otoi(ptr, len); } struct tarerofs_xattr_item { struct list_head list; char *kv; unsigned int len, namelen; }; int tarerofs_insert_xattr(struct list_head *xattrs, char *kv, int namelen, int len, bool skip) { struct tarerofs_xattr_item *item; char *nv; DBG_BUGON(namelen >= len); list_for_each_entry(item, xattrs, list) { if (!strncmp(item->kv, kv, namelen + 1)) { if (skip) return 0; goto found; } } item = malloc(sizeof(*item)); if (!item) return -ENOMEM; item->kv = NULL; item->namelen = namelen; namelen = 0; list_add_tail(&item->list, xattrs); found: nv = realloc(item->kv, len); if (!nv) return -ENOMEM; item->kv = nv; item->len = len; memcpy(nv + namelen, kv + namelen, len - namelen); return 0; } int tarerofs_merge_xattrs(struct list_head *dst, struct list_head *src) { struct tarerofs_xattr_item *item; list_for_each_entry(item, src, list) { int ret; ret = tarerofs_insert_xattr(dst, item->kv, item->namelen, item->len, true); if (ret) return ret; } return 0; } void tarerofs_remove_xattrs(struct list_head *xattrs) { struct tarerofs_xattr_item *item, *n; list_for_each_entry_safe(item, n, xattrs, list) { DBG_BUGON(!item->kv); free(item->kv); list_del(&item->list); free(item); } } int tarerofs_apply_xattrs(struct erofs_inode *inode, struct list_head *xattrs) { struct tarerofs_xattr_item *item; int ret; list_for_each_entry(item, xattrs, list) { const char *v = item->kv + item->namelen + 1; unsigned int vsz = item->len - item->namelen - 1; if (item->len <= item->namelen - 1) { DBG_BUGON(item->len < item->namelen - 1); continue; } item->kv[item->namelen] = '\0'; erofs_dbg("Recording xattr(%s)=\"%s\" (of %u bytes) to file %s", item->kv, v, vsz, inode->i_srcpath); ret = erofs_setxattr(inode, item->kv, v, vsz); if (ret == -ENODATA) erofs_err("Failed to set xattr(%s)=%s to file %s", item->kv, v, inode->i_srcpath); else if (ret) return ret; } return 0; } static const char lookup_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; static int base64_decode(const char *src, int len, u8 *dst) { int i, bits = 0, ac = 0; const char *p; u8 *cp = dst; if(!(len % 4)) { /* Check for and ignore any end padding */ if (src[len - 2] == '=' && src[len - 1] == '=') len -= 2; else if (src[len - 1] == '=') --len; } for (i = 0; i < len; i++) { p = strchr(lookup_table, src[i]); if (p == NULL || src[i] == 0) return -2; ac += (p - lookup_table) << bits; bits += 6; if (bits >= 8) { *cp++ = ac & 0xff; ac >>= 8; bits -= 8; } } if (ac) return -1; return cp - dst; } int tarerofs_parse_pax_header(struct erofs_iostream *ios, struct erofs_pax_header *eh, u32 size) { char *buf, *p; int ret; buf = malloc(size); if (!buf) return -ENOMEM; p = buf; ret = erofs_iostream_bread(ios, buf, size); if (ret != size) goto out; while (p < buf + size) { char *kv, *value; int len, n; /* extended records are of the format: "LEN NAME=VALUE\n" */ ret = sscanf(p, "%d %n", &len, &n); if (ret < 1 || len <= n || len > buf + size - p) { ret = -EIO; goto out; } kv = p + n; p += len; len -= n; if (p[-1] != '\n') { ret = -EIO; goto out; } p[-1] = '\0'; value = memchr(kv, '=', p - kv); if (!value) { ret = -EIO; goto out; } else { long long lln; value++; if (!strncmp(kv, "path=", sizeof("path=") - 1)) { int j = p - 1 - value; free(eh->path); eh->path = strdup(value); while (eh->path[j - 1] == '/') eh->path[--j] = '\0'; } else if (!strncmp(kv, "linkpath=", sizeof("linkpath=") - 1)) { free(eh->link); eh->link = strdup(value); } else if (!strncmp(kv, "mtime=", sizeof("mtime=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1) { ret = -EIO; goto out; } eh->st.st_mtime = lln; if (value[n] == '.') { ret = sscanf(value + n + 1, "%d", &n); if (ret < 1) { ret = -EIO; goto out; } ST_MTIM_NSEC_SET(&eh->st, n); } else { ST_MTIM_NSEC_SET(&eh->st, 0); } eh->use_mtime = true; } else if (!strncmp(kv, "size=", sizeof("size=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_size = lln; eh->use_size = true; } else if (!strncmp(kv, "uid=", sizeof("uid=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_uid = lln; eh->use_uid = true; } else if (!strncmp(kv, "gid=", sizeof("gid=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_gid = lln; eh->use_gid = true; } else if (!strncmp(kv, "SCHILY.xattr.", sizeof("SCHILY.xattr.") - 1)) { char *key = kv + sizeof("SCHILY.xattr.") - 1; --len; /* p[-1] == '\0' */ ret = tarerofs_insert_xattr(&eh->xattrs, key, value - key - 1, len - (key - kv), false); if (ret) goto out; } else if (!strncmp(kv, "LIBARCHIVE.xattr.", sizeof("LIBARCHIVE.xattr.") - 1)) { char *key; key = kv + sizeof("LIBARCHIVE.xattr.") - 1; --len; /* p[-1] == '\0' */ ret = base64_decode(value, len - (value - kv), (u8 *)value); if (ret < 0) { ret = -EFSCORRUPTED; goto out; } ret = tarerofs_insert_xattr(&eh->xattrs, key, value - key - 1, value - key + ret, false); if (ret) goto out; } else { erofs_info("unrecognized pax keyword \"%s\", ignoring", kv); } } } ret = 0; out: free(buf); return ret; } void tarerofs_remove_inode(struct erofs_inode *inode) { struct erofs_dentry *d; --inode->i_nlink; if (!S_ISDIR(inode->i_mode)) return; /* remove all subdirss */ list_for_each_entry(d, &inode->i_subdirs, d_child) { if (!is_dot_dotdot(d->name)) tarerofs_remove_inode(d->inode); erofs_iput(d->inode); d->inode = NULL; } --inode->i_parent->i_nlink; } static int tarerofs_write_file_data(struct erofs_inode *inode, struct erofs_tarfile *tar) { void *buf; int fd, nread; u64 off, j; if (!inode->i_diskbuf) { inode->i_diskbuf = calloc(1, sizeof(*inode->i_diskbuf)); if (!inode->i_diskbuf) return -ENOSPC; } else { erofs_diskbuf_close(inode->i_diskbuf); } fd = erofs_diskbuf_reserve(inode->i_diskbuf, 0, &off); if (fd < 0) return -EBADF; for (j = inode->i_size; j; ) { nread = erofs_iostream_read(&tar->ios, &buf, j); if (nread < 0) break; if (write(fd, buf, nread) != nread) { nread = -EIO; break; } j -= nread; } erofs_diskbuf_commit(inode->i_diskbuf, inode->i_size); inode->datasource = EROFS_INODE_DATA_SOURCE_DISKBUF; return 0; } int tarerofs_parse_tar(struct erofs_inode *root, struct erofs_tarfile *tar) { char path[PATH_MAX]; struct erofs_pax_header eh = tar->global; struct erofs_sb_info *sbi = root->sbi; bool whout, opq, e = false; struct stat st; erofs_off_t tar_offset, dataoff; struct tar_header *th; struct erofs_dentry *d; struct erofs_inode *inode; unsigned int j, csum, cksum; int ckksum, ret, rem; if (eh.path) eh.path = strdup(eh.path); if (eh.link) eh.link = strdup(eh.link); init_list_head(&eh.xattrs); restart: rem = tar->offset & 511; if (rem) { if (erofs_iostream_lskip(&tar->ios, 512 - rem)) { ret = -EIO; goto out; } tar->offset += 512 - rem; } tar_offset = tar->offset; ret = erofs_iostream_read(&tar->ios, (void **)&th, sizeof(*th)); if (ret != sizeof(*th)) { if (tar->headeronly_mode || tar->ddtaridx_mode) { ret = 1; goto out; } erofs_err("failed to read header block @ %llu", tar_offset); ret = -EIO; goto out; } tar->offset += sizeof(*th); if (*th->name == '\0') { if (e) { /* end of tar 2 empty blocks */ ret = 1; goto out; } e = true; /* empty jump to next block */ goto restart; } /* chksum field itself treated as ' ' */ csum = tarerofs_otoi(th->chksum, sizeof(th->chksum)); if (errno) { erofs_err("invalid chksum @ %llu", tar_offset); ret = -EBADMSG; goto out; } cksum = 0; for (j = 0; j < 8; ++j) cksum += (unsigned int)' '; ckksum = cksum; for (j = 0; j < 148; ++j) { cksum += (unsigned int)((u8*)th)[j]; ckksum += (int)((char*)th)[j]; } for (j = 156; j < 500; ++j) { cksum += (unsigned int)((u8*)th)[j]; ckksum += (int)((char*)th)[j]; } if (!tar->ddtaridx_mode && csum != cksum && csum != ckksum) { erofs_err("chksum mismatch @ %llu", tar_offset); ret = -EBADMSG; goto out; } if (th->typeflag == GNUTYPE_VOLHDR) { if (th->size[0]) erofs_warn("GNUTYPE_VOLHDR with non-zeroed size @ %llu", tar_offset); /* anyway, strncpy could cause some GCC warning here */ memcpy(sbi->volume_name, th->name, sizeof(sbi->volume_name)); goto restart; } if (memcmp(th->magic, "ustar", 5)) { erofs_err("invalid tar magic @ %llu", tar_offset); ret = -EIO; goto out; } st.st_mode = tarerofs_otoi(th->mode, sizeof(th->mode)); if (errno) goto invalid_tar; if (eh.use_uid) { st.st_uid = eh.st.st_uid; } else { st.st_uid = tarerofs_parsenum(th->uid, sizeof(th->uid)); if (errno) goto invalid_tar; } if (eh.use_gid) { st.st_gid = eh.st.st_gid; } else { st.st_gid = tarerofs_parsenum(th->gid, sizeof(th->gid)); if (errno) goto invalid_tar; } if (eh.use_size) { st.st_size = eh.st.st_size; } else { st.st_size = tarerofs_parsenum(th->size, sizeof(th->size)); if (errno) goto invalid_tar; } if (eh.use_mtime) { st.st_mtime = eh.st.st_mtime; ST_MTIM_NSEC_SET(&st, ST_MTIM_NSEC(&eh.st)); } else { st.st_mtime = tarerofs_parsenum(th->mtime, sizeof(th->mtime)); if (errno) goto invalid_tar; ST_MTIM_NSEC_SET(&st, 0); } if (th->typeflag <= '7' && !eh.path) { eh.path = path; j = 0; if (*th->prefix) { memcpy(path, th->prefix, sizeof(th->prefix)); path[sizeof(th->prefix)] = '\0'; j = strlen(path); if (path[j - 1] != '/') { path[j] = '/'; path[++j] = '\0'; } } memcpy(path + j, th->name, sizeof(th->name)); path[j + sizeof(th->name)] = '\0'; j = strlen(path); while (path[j - 1] == '/') path[--j] = '\0'; } dataoff = tar->offset; if (!(tar->headeronly_mode || tar->ddtaridx_mode)) tar->offset += st.st_size; switch(th->typeflag) { case '0': case '7': case '1': st.st_mode |= S_IFREG; break; case '2': st.st_mode |= S_IFLNK; break; case '3': st.st_mode |= S_IFCHR; break; case '4': st.st_mode |= S_IFBLK; break; case '5': st.st_mode |= S_IFDIR; break; case '6': st.st_mode |= S_IFIFO; break; case 'g': ret = tarerofs_parse_pax_header(&tar->ios, &tar->global, st.st_size); if (ret) goto out; if (tar->global.path) { free(eh.path); eh.path = strdup(tar->global.path); } if (tar->global.link) { free(eh.link); eh.link = strdup(tar->global.link); } goto restart; case 'x': ret = tarerofs_parse_pax_header(&tar->ios, &eh, st.st_size); if (ret) goto out; goto restart; case 'L': free(eh.path); eh.path = malloc(st.st_size + 1); if (st.st_size != erofs_iostream_bread(&tar->ios, eh.path, st.st_size)) goto invalid_tar; eh.path[st.st_size] = '\0'; goto restart; case 'K': free(eh.link); eh.link = malloc(st.st_size + 1); if (st.st_size > PATH_MAX || st.st_size != erofs_iostream_bread(&tar->ios, eh.link, st.st_size)) goto invalid_tar; eh.link[st.st_size] = '\0'; goto restart; default: erofs_info("unrecognized typeflag %xh @ %llu - ignoring", th->typeflag, tar_offset); (void)erofs_iostream_lskip(&tar->ios, st.st_size); ret = 0; goto out; } st.st_rdev = 0; if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) { int major, minor; major = tarerofs_parsenum(th->devmajor, sizeof(th->devmajor)); if (errno) { erofs_err("invalid device major @ %llu", tar_offset); goto out; } minor = tarerofs_parsenum(th->devminor, sizeof(th->devminor)); if (errno) { erofs_err("invalid device minor @ %llu", tar_offset); goto out; } st.st_rdev = (major << 8) | (minor & 0xff) | ((minor & ~0xff) << 12); } else if (th->typeflag == '1' || th->typeflag == '2') { if (!eh.link) eh.link = strndup(th->linkname, sizeof(th->linkname)); } /* EROFS metadata index referring to the original tar data */ if (tar->index_mode && sbi->extra_devices && erofs_blkoff(sbi, dataoff)) { erofs_err("invalid tar data alignment @ %llu", tar_offset); ret = -EIO; goto out; } erofs_dbg("parsing %s (mode %05o)", eh.path, st.st_mode); d = erofs_rebuild_get_dentry(root, eh.path, tar->aufs, &whout, &opq, true); if (IS_ERR(d)) { ret = PTR_ERR(d); goto out; } if (!d) { /* some tarballs include '.' which indicates the root directory */ if (!S_ISDIR(st.st_mode)) { ret = -ENOTDIR; goto out; } inode = root; } else if (opq) { DBG_BUGON(d->type == EROFS_FT_UNKNOWN); DBG_BUGON(!d->inode); /* * needed if the tar tree is used soon, thus we have no chance * to generate it from xattrs. No impact to mergefs. */ d->inode->opaque = true; ret = erofs_set_opaque_xattr(d->inode); goto out; } else if (th->typeflag == '1') { /* hard link cases */ struct erofs_dentry *d2; bool dumb; if (S_ISDIR(st.st_mode)) { ret = -EISDIR; goto out; } if (d->type != EROFS_FT_UNKNOWN) { tarerofs_remove_inode(d->inode); erofs_iput(d->inode); } d->inode = NULL; d2 = erofs_rebuild_get_dentry(root, eh.link, tar->aufs, &dumb, &dumb, false); if (IS_ERR(d2)) { ret = PTR_ERR(d2); goto out; } if (d2->type == EROFS_FT_UNKNOWN) { ret = -ENOENT; goto out; } if (S_ISDIR(d2->inode->i_mode)) { ret = -EISDIR; goto out; } inode = erofs_igrab(d2->inode); d->inode = inode; d->type = d2->type; ++inode->i_nlink; ret = 0; goto out; } else if (d->type != EROFS_FT_UNKNOWN) { if (d->type != EROFS_FT_DIR || !S_ISDIR(st.st_mode)) { struct erofs_inode *parent = d->inode->i_parent; tarerofs_remove_inode(d->inode); erofs_iput(d->inode); d->inode = parent; goto new_inode; } inode = d->inode; } else { new_inode: inode = erofs_new_inode(sbi); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto out; } inode->dev = tar->dev; inode->i_parent = d->inode; d->inode = inode; d->type = erofs_mode_to_ftype(st.st_mode); } if (whout) { inode->i_mode = (inode->i_mode & ~S_IFMT) | S_IFCHR; inode->u.i_rdev = EROFS_WHITEOUT_DEV; d->type = EROFS_FT_CHRDEV; /* * Mark the parent directory as copied-up to avoid exposing * whiteouts if mounted. See kernel commit b79e05aaa166 * ("ovl: no direct iteration for dir with origin xattr") */ inode->i_parent->whiteouts = true; } else { inode->i_mode = st.st_mode; if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) inode->u.i_rdev = erofs_new_encode_dev(st.st_rdev); } inode->i_srcpath = strdup(eh.path); if (!inode->i_srcpath) { ret = -ENOMEM; goto out; } ret = __erofs_fill_inode(inode, &st, eh.path); if (ret) goto out; inode->i_size = st.st_size; if (!S_ISDIR(inode->i_mode)) { if (S_ISLNK(inode->i_mode)) { inode->i_size = strlen(eh.link); inode->i_link = malloc(inode->i_size + 1); memcpy(inode->i_link, eh.link, inode->i_size + 1); } else if (inode->i_size) { if (tar->headeronly_mode) { ret = erofs_write_zero_inode(inode); } else if (tar->ddtaridx_mode) { dataoff = le64_to_cpu(*(__le64 *)(th->devmajor)); if (tar->rvsp_mode) { inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP; inode->i_ino[1] = dataoff; ret = 0; } else { ret = tarerofs_write_chunkes(inode, dataoff); } } else if (tar->rvsp_mode) { inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP; inode->i_ino[1] = dataoff; if (erofs_iostream_lskip(&tar->ios, inode->i_size)) ret = -EIO; else ret = 0; } else if (tar->index_mode) { ret = tarerofs_write_chunkes(inode, dataoff); if (!ret && erofs_iostream_lskip(&tar->ios, inode->i_size)) ret = -EIO; } else { ret = tarerofs_write_file_data(inode, tar); } if (ret) goto out; } inode->i_nlink++; } else if (!inode->i_nlink) { ret = erofs_init_empty_dir(inode); if (ret) goto out; } ret = tarerofs_merge_xattrs(&eh.xattrs, &tar->global.xattrs); if (ret) goto out; ret = tarerofs_apply_xattrs(inode, &eh.xattrs); out: if (eh.path != path) free(eh.path); free(eh.link); tarerofs_remove_xattrs(&eh.xattrs); return ret; invalid_tar: erofs_err("invalid tar @ %llu", tar_offset); ret = -EIO; goto out; }