set_xattrs(struct archive_write_disk *a)
{
	struct archive_entry *entry = a->entry;
	struct archive_string errlist;
	int ret = ARCHIVE_OK;
	int i = archive_entry_xattr_reset(entry);
	short fail = 0;

	archive_string_init(&errlist);

	while (i--) {
		const char *name;
		const void *value;
		size_t size;
		int e;

		archive_entry_xattr_next(entry, &name, &value, &size);

		if (name == NULL)
			continue;
#if ARCHIVE_XATTR_LINUX
		/* Linux: quietly skip POSIX.1e ACL extended attributes */
		if (strncmp(name, "system.", 7) == 0 &&
		   (strcmp(name + 7, "posix_acl_access") == 0 ||
		    strcmp(name + 7, "posix_acl_default") == 0))
			continue;
		if (strncmp(name, "trusted.SGI_", 12) == 0 &&
		   (strcmp(name + 12, "ACL_DEFAULT") == 0 ||
		    strcmp(name + 12, "ACL_FILE") == 0))
			continue;

		/* Linux: xfsroot namespace is obsolete and unsupported */
		if (strncmp(name, "xfsroot.", 8) == 0) {
			fail = 1;
			archive_strcat(&errlist, name);
			archive_strappend_char(&errlist, ' ');
			continue;
		}
#endif

		if (a->fd >= 0) {
#if ARCHIVE_XATTR_LINUX
			e = fsetxattr(a->fd, name, value, size, 0);
#elif ARCHIVE_XATTR_DARWIN
			e = fsetxattr(a->fd, name, value, size, 0, 0);
#elif ARCHIVE_XATTR_AIX
			e = fsetea(a->fd, name, value, size, 0);
#endif
		} else {
#if ARCHIVE_XATTR_LINUX
			e = lsetxattr(archive_entry_pathname(entry),
			    name, value, size, 0);
#elif ARCHIVE_XATTR_DARWIN
			e = setxattr(archive_entry_pathname(entry),
			    name, value, size, 0, XATTR_NOFOLLOW);
#elif ARCHIVE_XATTR_AIX
			e = lsetea(archive_entry_pathname(entry),
			    name, value, size, 0);
#endif
		}
		if (e == -1) {
			ret = ARCHIVE_WARN;
			archive_strcat(&errlist, name);
			archive_strappend_char(&errlist, ' ');
			if (errno != ENOTSUP && errno != ENOSYS)
				fail = 1;
		}
	}

	if (ret == ARCHIVE_WARN) {
		if (fail && errlist.length > 0) {
			errlist.length--;
			errlist.s[errlist.length] = '\0';
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Cannot restore extended attributes: %s",
			    errlist.s);
		} else
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Cannot restore extended "
			    "attributes on this file system.");
	}

	archive_string_free(&errlist);
	return (ret);
}


Never follow symlinks when setting file flags on Linux

When opening a file descriptor to set file flags on linux, ensure
no symbolic links are followed. This fixes the case when an archive
contains a directory entry followed by a symlink entry with the same
path. The fixup code would modify file flags of the symlink target.

archive_write_disk_set_skip_file(struct archive *_a, la_int64_t d, la_int64_t i)
{
	struct archive_write_disk *a = (struct archive_write_disk *)_a;
	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
	    ARCHIVE_STATE_ANY, "archive_write_disk_set_skip_file");
	a->skip_file_set = 1;
	a->skip_file_dev = d;
	a->skip_file_ino = i;
	return (ARCHIVE_OK);
}

An improper link resolution flaw can occur while extracting an archive leading to changing modes, times, access control lists, and flags of a file outside of the archive. An attacker may provide a malicious archive to a victim user, who would trigger this flaw when trying to extract the archive. A local attacker may use this flaw to gain more privileges in a system.


create_parent_dir(struct archive_write_disk *a, char *path)
{
	char *slash;
	int r;

	/* Remove tail element to obtain parent name. */
	slash = strrchr(path, '/');
	if (slash == NULL)
		return (ARCHIVE_OK);
	*slash = '\0';
	r = create_dir(a, path);
	*slash = '/';
	return (r);
}


set_ownership(struct archive_write_disk *a)
{
#if !defined(__CYGWIN__) && !defined(__linux__)
/*
 * On Linux, a process may have the CAP_CHOWN capability.
 * On Windows there is no 'root' user with uid 0.
 * Elsewhere we can skip calling chown if we are not root and the desired
 * user id does not match the current user.
 */
	if (a->user_uid != 0 && a->user_uid != a->uid) {
		archive_set_error(&a->archive, errno,
		    "Can't set UID=%jd", (intmax_t)a->uid);
		return (ARCHIVE_WARN);
	}
#endif

#ifdef HAVE_FCHOWN
	/* If we have an fd, we can avoid a race. */
	if (a->fd >= 0 && fchown(a->fd, a->uid, a->gid) == 0) {
		/* We've set owner and know uid/gid are correct. */
		a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
		return (ARCHIVE_OK);
	}
#endif

	/* We prefer lchown() but will use chown() if that's all we have. */
	/* Of course, if we have neither, this will always fail. */
#ifdef HAVE_LCHOWN
	if (lchown(a->name, a->uid, a->gid) == 0) {
		/* We've set owner and know uid/gid are correct. */
		a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
		return (ARCHIVE_OK);
	}
#elif HAVE_CHOWN
	if (!S_ISLNK(a->mode) && chown(a->name, a->uid, a->gid) == 0) {
		/* We've set owner and know uid/gid are correct. */
		a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
		return (ARCHIVE_OK);
	}
#endif

	archive_set_error(&a->archive, errno,
	    "Can't set user=%jd/group=%jd for %s",
	    (intmax_t)a->uid, (intmax_t)a->gid, a->name);
	return (ARCHIVE_WARN);
}


set_time_tru64(int fd, int mode, const char *name,
    time_t atime, long atime_nsec,
    time_t mtime, long mtime_nsec,
    time_t ctime, long ctime_nsec)
{
	struct attr_timbuf tstamp;
	tstamp.atime.tv_sec = atime;
	tstamp.mtime.tv_sec = mtime;
	tstamp.ctime.tv_sec = ctime;
#if defined (__hpux) && defined (__ia64)
	tstamp.atime.tv_nsec = atime_nsec;
	tstamp.mtime.tv_nsec = mtime_nsec;
	tstamp.ctime.tv_nsec = ctime_nsec;
#else
	tstamp.atime.tv_usec = atime_nsec / 1000;
	tstamp.mtime.tv_usec = mtime_nsec / 1000;
	tstamp.ctime.tv_usec = ctime_nsec / 1000;
#endif
	return (fcntl(fd,F_SETTIMES,&tstamp));
}


_archive_write_disk_data(struct archive *_a, const void *buff, size_t size)
{
	struct archive_write_disk *a = (struct archive_write_disk *)_a;

	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
	    ARCHIVE_STATE_DATA, "archive_write_data");

	if (a->todo & TODO_HFS_COMPRESSION)
		return (hfs_write_data_block(a, buff, size));
	return (write_data_block(a, buff, size));
}


fixup_appledouble(struct archive_write_disk *a, const char *pathname)
{
	char buff[8];
	struct stat st;
	const char *p;
	struct archive_string datafork;
	int fd = -1, ret = ARCHIVE_OK;

	archive_string_init(&datafork);
	/* Check if the current file name is a type of the resource
	 * fork file. */
	p = strrchr(pathname, '/');
	if (p == NULL)
		p = pathname;
	else
		p++;
	if (p[0] != '.' || p[1] != '_')
		goto skip_appledouble;

	/*
	 * Check if the data fork file exists.
	 *
	 * TODO: Check if this write disk object has handled it.
	 */
	archive_strncpy(&datafork, pathname, p - pathname);
	archive_strcat(&datafork, p + 2);
	if (lstat(datafork.s, &st) == -1 ||
	    (st.st_mode & AE_IFMT) != AE_IFREG)
		goto skip_appledouble;

	/*
	 * Check if the file is in the AppleDouble form.
	 */
	fd = open(pathname, O_RDONLY | O_BINARY | O_CLOEXEC);
	__archive_ensure_cloexec_flag(fd);
	if (fd == -1) {
		archive_set_error(&a->archive, errno,
		    "Failed to open a restoring file");
		ret = ARCHIVE_WARN;
		goto skip_appledouble;
	}
	if (read(fd, buff, 8) == -1) {
		archive_set_error(&a->archive, errno,
		    "Failed to read a restoring file");
		close(fd);
		ret = ARCHIVE_WARN;
		goto skip_appledouble;
	}
	close(fd);
	/* Check AppleDouble Magic Code. */
	if (archive_be32dec(buff) != 0x00051607)
		goto skip_appledouble;
	/* Check AppleDouble Version. */
	if (archive_be32dec(buff+4) != 0x00020000)
		goto skip_appledouble;

	ret = copy_metadata(a, pathname, datafork.s,
#if defined(UF_COMPRESSED)
	    st.st_flags & UF_COMPRESSED);
#else
	    0);
#endif
	if (ret == ARCHIVE_OK) {
		unlink(pathname);
		ret = ARCHIVE_EOF;
	}
skip_appledouble:
	archive_string_free(&datafork);
	return (ret);
}


_archive_write_disk_free(struct archive *_a)
{
	struct archive_write_disk *a;
	int ret;
	if (_a == NULL)
		return (ARCHIVE_OK);
	archive_check_magic(_a, ARCHIVE_WRITE_DISK_MAGIC,
	    ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_write_disk_free");
	a = (struct archive_write_disk *)_a;
	ret = _archive_write_disk_close(&a->archive);
	archive_write_disk_set_group_lookup(&a->archive, NULL, NULL, NULL);
	archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL);
	archive_entry_free(a->entry);
	archive_string_free(&a->_name_data);
	archive_string_free(&a->_tmpname_data);
	archive_string_free(&a->archive.error_string);
	archive_string_free(&a->path_safe);
	a->archive.magic = 0;
	__archive_clean(&a->archive);
	free(a->decmpfs_header_p);
	free(a->resource_fork);
	free(a->compressed_buffer);
	free(a->uncompressed_buffer);
#if defined(__APPLE__) && defined(UF_COMPRESSED) && defined(HAVE_SYS_XATTR_H)\
	&& defined(HAVE_ZLIB_H)
	if (a->stream_valid) {
		switch (deflateEnd(&a->stream)) {
		case Z_OK:
			break;
		default:
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Failed to clean up compressor");
			ret = ARCHIVE_FATAL;
			break;
		}
	}
#endif
	free(a);
	return (ret);
}


la_opendirat(int fd, const char *path) {
	const int flags = O_CLOEXEC
#if defined(O_BINARY)
	    | O_BINARY
#endif
#if defined(O_DIRECTORY)
	    | O_DIRECTORY
#endif
#if defined(O_PATH)
	    | O_PATH
#elif defined(O_SEARCH)
	    | O_SEARCH
#elif defined(__FreeBSD__) && defined(O_EXEC)
	    | O_EXEC
#else
	    | O_RDONLY
#endif
	    ;

#if !defined(HAVE_OPENAT)
	if (fd != AT_FDCWD) {
		errno = ENOTSUP;
		return (-1);
	} else
		return (open(path, flags));
#else
	return (openat(fd, path, flags));
#endif
}


hfs_write_resource_fork_header(struct archive_write_disk *a)
{
	unsigned char *buff;
	uint32_t rsrc_bytes;
	uint32_t rsrc_header_bytes;

	/*
	 * Write resource fork header + block info.
	 */
	buff = a->resource_fork;
	rsrc_bytes = a->compressed_rsrc_position - RSRC_F_SIZE;
	rsrc_header_bytes =
		RSRC_H_SIZE +		/* Header base size. */
		4 +			/* Block count. */
		(a->decmpfs_block_count * 8);/* Block info */
	archive_be32enc(buff, 0x100);
	archive_be32enc(buff + 4, rsrc_bytes);
	archive_be32enc(buff + 8, rsrc_bytes - 256);
	archive_be32enc(buff + 12, 0x32);
	memset(buff + 16, 0, 240);
	archive_be32enc(buff + 256, rsrc_bytes - 260);
	return hfs_write_resource_fork(a, buff, rsrc_header_bytes, 0);
}


set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
    mode_t mode, unsigned long set, unsigned long clear)
{
	int		 ret;
	int		 myfd = fd;
	int newflags, oldflags;
	/*
	 * Linux has no define for the flags that are only settable by
	 * the root user.  This code may seem a little complex, but
	 * there seem to be some Linux systems that lack these
	 * defines. (?)  The code below degrades reasonably gracefully
	 * if sf_mask is incomplete.
	 */
	const int sf_mask = 0
#if defined(FS_IMMUTABLE_FL)
	    | FS_IMMUTABLE_FL
#elif defined(EXT2_IMMUTABLE_FL)
	    | EXT2_IMMUTABLE_FL
#endif
#if defined(FS_APPEND_FL)
	    | FS_APPEND_FL
#elif defined(EXT2_APPEND_FL)
	    | EXT2_APPEND_FL
#endif
#if defined(FS_JOURNAL_DATA_FL)
	    | FS_JOURNAL_DATA_FL
#endif
	;

	if (set == 0 && clear == 0)
		return (ARCHIVE_OK);
	/* Only regular files and dirs can have flags. */
	if (!S_ISREG(mode) && !S_ISDIR(mode))
		return (ARCHIVE_OK);

	/* If we weren't given an fd, open it ourselves. */
	if (myfd < 0) {
		myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY |
		    O_CLOEXEC | O_NOFOLLOW);
		__archive_ensure_cloexec_flag(myfd);
	}
	if (myfd < 0)
		return (ARCHIVE_OK);

	/*
	 * XXX As above, this would be way simpler if we didn't have
	 * to read the current flags from disk. XXX
	 */
	ret = ARCHIVE_OK;

	/* Read the current file flags. */
	if (ioctl(myfd,
#ifdef FS_IOC_GETFLAGS
	    FS_IOC_GETFLAGS,
#else
	    EXT2_IOC_GETFLAGS,
#endif
	    &oldflags) < 0)
		goto fail;

	/* Try setting the flags as given. */
	newflags = (oldflags & ~clear) | set;
	if (ioctl(myfd,
#ifdef FS_IOC_SETFLAGS
	    FS_IOC_SETFLAGS,
#else
	    EXT2_IOC_SETFLAGS,
#endif
	    &newflags) >= 0)
		goto cleanup;
	if (errno != EPERM)
		goto fail;

	/* If we couldn't set all the flags, try again with a subset. */
	newflags &= ~sf_mask;
	oldflags &= sf_mask;
	newflags |= oldflags;
	if (ioctl(myfd,
#ifdef FS_IOC_SETFLAGS
	    FS_IOC_SETFLAGS,
#else
	    EXT2_IOC_SETFLAGS,
#endif
	    &newflags) >= 0)
		goto cleanup;

	/* We couldn't set the flags, so report the failure. */
fail:
	archive_set_error(&a->archive, errno,
	    "Failed to set file flags");
	ret = ARCHIVE_WARN;
cleanup:
	if (fd < 0)
		close(myfd);
	return (ret);
}


fixup_appledouble(struct archive_write_disk *a, const char *pathname)
{
	(void)a; /* UNUSED */
	(void)pathname; /* UNUSED */
	return (ARCHIVE_OK);
}


set_times(struct archive_write_disk *a,
    int fd, int mode, const char *name,
    time_t atime, long atime_nanos,
    time_t birthtime, long birthtime_nanos,
    time_t mtime, long mtime_nanos,
    time_t cctime, long ctime_nanos)
{
	/* Note: set_time doesn't use libarchive return conventions!
	 * It uses syscall conventions.  So 0 here instead of ARCHIVE_OK. */
	int r1 = 0, r2 = 0;

#ifdef F_SETTIMES
	 /*
	 * on Tru64 try own fcntl first which can restore even the
	 * ctime, fall back to default code path below if it fails
	 * or if we are not running as root
	 */
	if (a->user_uid == 0 &&
	    set_time_tru64(fd, mode, name,
			   atime, atime_nanos, mtime,
			   mtime_nanos, cctime, ctime_nanos) == 0) {
		return (ARCHIVE_OK);
	}
#else /* Tru64 */
	(void)cctime; /* UNUSED */
	(void)ctime_nanos; /* UNUSED */
#endif /* Tru64 */

#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
	/*
	 * If you have struct stat.st_birthtime, we assume BSD
	 * birthtime semantics, in which {f,l,}utimes() updates
	 * birthtime to earliest mtime.  So we set the time twice,
	 * first using the birthtime, then using the mtime.  If
	 * birthtime == mtime, this isn't necessary, so we skip it.
	 * If birthtime > mtime, then this won't work, so we skip it.
	 */
	if (birthtime < mtime
	    || (birthtime == mtime && birthtime_nanos < mtime_nanos))
		r1 = set_time(fd, mode, name,
			      atime, atime_nanos,
			      birthtime, birthtime_nanos);
#else
	(void)birthtime; /* UNUSED */
	(void)birthtime_nanos; /* UNUSED */
#endif
	r2 = set_time(fd, mode, name,
		      atime, atime_nanos,
		      mtime, mtime_nanos);
	if (r1 != 0 || r2 != 0) {
		archive_set_error(&a->archive, errno,
				  "Can't restore time");
		return (ARCHIVE_WARN);
	}
	return (ARCHIVE_OK);
}


set_times_from_entry(struct archive_write_disk *a)
{
	time_t atime, birthtime, mtime, cctime;
	long atime_nsec, birthtime_nsec, mtime_nsec, ctime_nsec;

	/* Suitable defaults. */
	atime = birthtime = mtime = cctime = a->start_time;
	atime_nsec = birthtime_nsec = mtime_nsec = ctime_nsec = 0;

	/* If no time was provided, we're done. */
	if (!archive_entry_atime_is_set(a->entry)
#if HAVE_STRUCT_STAT_ST_BIRTHTIME
	    && !archive_entry_birthtime_is_set(a->entry)
#endif
	    && !archive_entry_mtime_is_set(a->entry))
		return (ARCHIVE_OK);

	if (archive_entry_atime_is_set(a->entry)) {
		atime = archive_entry_atime(a->entry);
		atime_nsec = archive_entry_atime_nsec(a->entry);
	}
	if (archive_entry_birthtime_is_set(a->entry)) {
		birthtime = archive_entry_birthtime(a->entry);
		birthtime_nsec = archive_entry_birthtime_nsec(a->entry);
	}
	if (archive_entry_mtime_is_set(a->entry)) {
		mtime = archive_entry_mtime(a->entry);
		mtime_nsec = archive_entry_mtime_nsec(a->entry);
	}
	if (archive_entry_ctime_is_set(a->entry)) {
		cctime = archive_entry_ctime(a->entry);
		ctime_nsec = archive_entry_ctime_nsec(a->entry);
	}

	return set_times(a, a->fd, a->mode, a->name,
			 atime, atime_nsec,
			 birthtime, birthtime_nsec,
			 mtime, mtime_nsec,
			 cctime, ctime_nsec);
}


hfs_decompress(struct archive_write_disk *a)
{
	uint32_t *block_info;
	unsigned int block_count;
	uint32_t data_pos, data_size;
	ssize_t r;
	ssize_t bytes_written, bytes_to_write;
	unsigned char *b;

	block_info = (uint32_t *)(a->resource_fork + RSRC_H_SIZE);
	block_count = archive_le32dec(block_info++);
	while (block_count--) {
		data_pos = RSRC_H_SIZE + archive_le32dec(block_info++);
		data_size = archive_le32dec(block_info++);
		r = fgetxattr(a->fd, XATTR_RESOURCEFORK_NAME,
		    a->compressed_buffer, data_size, data_pos, 0);
		if (r != data_size)  {
			archive_set_error(&a->archive,
			    (r < 0)?errno:ARCHIVE_ERRNO_MISC,
			    "Failed to read resource fork");
			return (ARCHIVE_WARN);
		}
		if (a->compressed_buffer[0] == 0xff) {
			bytes_to_write = data_size -1;
			b = a->compressed_buffer + 1;
		} else {
			uLong dest_len = MAX_DECMPFS_BLOCK_SIZE;
			int zr;

			zr = uncompress((Bytef *)a->uncompressed_buffer,
			    &dest_len, a->compressed_buffer, data_size);
			if (zr != Z_OK) {
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_MISC,
				    "Failed to decompress resource fork");
				return (ARCHIVE_WARN);
			}
			bytes_to_write = dest_len;
			b = (unsigned char *)a->uncompressed_buffer;
		}
		do {
			bytes_written = write(a->fd, b, bytes_to_write);
			if (bytes_written < 0) {
				archive_set_error(&a->archive, errno,
				    "Write failed");
				return (ARCHIVE_WARN);
			}
			bytes_to_write -= bytes_written;
			b += bytes_written;
		} while (bytes_to_write > 0);
	}
	r = fremovexattr(a->fd, XATTR_RESOURCEFORK_NAME, 0);
	if (r == -1)  {
		archive_set_error(&a->archive, errno,
		    "Failed to remove resource fork");
		return (ARCHIVE_WARN);
	}
	return (ARCHIVE_OK);
}


set_xattrs(struct archive_write_disk *a)
{
	struct archive_entry *entry = a->entry;
	struct archive_string errlist;
	int ret = ARCHIVE_OK;
	int i = archive_entry_xattr_reset(entry);
	short fail = 0;

	archive_string_init(&errlist);

	while (i--) {
		const char *name;
		const void *value;
		size_t size;
		archive_entry_xattr_next(entry, &name, &value, &size);
		if (name != NULL) {
			int e;
			int namespace;

			namespace = EXTATTR_NAMESPACE_USER;

			if (strncmp(name, "user.", 5) == 0) {
				/* "user." attributes go to user namespace */
				name += 5;
				namespace = EXTATTR_NAMESPACE_USER;
			} else if (strncmp(name, "system.", 7) == 0) {
				name += 7;
				namespace = EXTATTR_NAMESPACE_SYSTEM;
				if (!strcmp(name, "nfs4.acl") ||
				    !strcmp(name, "posix1e.acl_access") ||
				    !strcmp(name, "posix1e.acl_default"))
					continue;
			} else {
				/* Other namespaces are unsupported */
				archive_strcat(&errlist, name);
				archive_strappend_char(&errlist, ' ');
				fail = 1;
				ret = ARCHIVE_WARN;
				continue;
			}

			if (a->fd >= 0) {
				/*
				 * On FreeBSD, extattr_set_fd does not
				 * return the same as
				 * extattr_set_file. It returns zero
				 * on success, non-zero on failure.
				 *
				 * We can detect the failure by
				 * manually setting errno prior to the
				 * call and checking after.
				 *
				 * If errno remains zero, fake the
				 * return value by setting e to size.
				 *
				 * This is a hack for now until I
				 * (Shawn Webb) get FreeBSD to fix the
				 * issue, if that's even possible.
				 */
				errno = 0;
				e = extattr_set_fd(a->fd, namespace, name,
				    value, size);
				if (e == 0 && errno == 0) {
					e = size;
				}
			} else {
				e = extattr_set_link(
				    archive_entry_pathname(entry), namespace,
				    name, value, size);
			}
			if (e != (int)size) {
				archive_strcat(&errlist, name);
				archive_strappend_char(&errlist, ' ');
				ret = ARCHIVE_WARN;
				if (errno != ENOTSUP && errno != ENOSYS)
					fail = 1;
			}
		}
	}

	if (ret == ARCHIVE_WARN) {
		if (fail && errlist.length > 0) {
			errlist.length--;
			errlist.s[errlist.length] = '\0';

			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Cannot restore extended attributes: %s",
			    errlist.s);
		} else
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Cannot restore extended "
			    "attributes on this file system.");
	}

	archive_string_free(&errlist);
	return (ret);
}


restore_entry(struct archive_write_disk *a)
{
	int ret = ARCHIVE_OK, en;

	if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) {
		/*
		 * TODO: Fix this.  Apparently, there are platforms
		 * that still allow root to hose the entire filesystem
		 * by unlinking a dir.  The S_ISDIR() test above
		 * prevents us from using unlink() here if the new
		 * object is a dir, but that doesn't mean the old
		 * object isn't a dir.
		 */
		if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
			(void)clear_nochange_fflags(a);
		if (unlink(a->name) == 0) {
			/* We removed it, reset cached stat. */
			a->pst = NULL;
		} else if (errno == ENOENT) {
			/* File didn't exist, that's just as good. */
		} else if (rmdir(a->name) == 0) {
			/* It was a dir, but now it's gone. */
			a->pst = NULL;
		} else {
			/* We tried, but couldn't get rid of it. */
			archive_set_error(&a->archive, errno,
			    "Could not unlink");
			return(ARCHIVE_FAILED);
		}
	}

	/* Try creating it first; if this fails, we'll try to recover. */
	en = create_filesystem_object(a);

	if ((en == ENOTDIR || en == ENOENT)
	    && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) {
		/* If the parent dir doesn't exist, try creating it. */
		create_parent_dir(a, a->name);
		/* Now try to create the object again. */
		en = create_filesystem_object(a);
	}

	if ((en == ENOENT) && (archive_entry_hardlink(a->entry) != NULL)) {
		archive_set_error(&a->archive, en,
		    "Hard-link target '%s' does not exist.",
		    archive_entry_hardlink(a->entry));
		return (ARCHIVE_FAILED);
	}

	if ((en == EISDIR || en == EEXIST)
	    && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
		/* If we're not overwriting, we're done. */
		if (S_ISDIR(a->mode)) {
			/* Don't overwrite any settings on existing directories. */
			a->todo = 0;
		}
		archive_entry_unset_size(a->entry);
		return (ARCHIVE_OK);
	}

	/*
	 * Some platforms return EISDIR if you call
	 * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some
	 * return EEXIST.  POSIX is ambiguous, requiring EISDIR
	 * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT)
	 * on an existing item.
	 */
	if (en == EISDIR) {
		/* A dir is in the way of a non-dir, rmdir it. */
		if (rmdir(a->name) != 0) {
			archive_set_error(&a->archive, errno,
			    "Can't remove already-existing dir");
			return (ARCHIVE_FAILED);
		}
		a->pst = NULL;
		/* Try again. */
		en = create_filesystem_object(a);
	} else if (en == EEXIST) {
		/*
		 * We know something is in the way, but we don't know what;
		 * we need to find out before we go any further.
		 */
		int r = 0;
		/*
		 * The SECURE_SYMLINKS logic has already removed a
		 * symlink to a dir if the client wants that.  So
		 * follow the symlink if we're creating a dir.
		 */
		if (S_ISDIR(a->mode))
			r = la_stat(a->name, &a->st);
		/*
		 * If it's not a dir (or it's a broken symlink),
		 * then don't follow it.
		 */
		if (r != 0 || !S_ISDIR(a->mode))
			r = lstat(a->name, &a->st);
		if (r != 0) {
			archive_set_error(&a->archive, errno,
			    "Can't stat existing object");
			return (ARCHIVE_FAILED);
		}

		/*
		 * NO_OVERWRITE_NEWER doesn't apply to directories.
		 */
		if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER)
		    &&  !S_ISDIR(a->st.st_mode)) {
			if (!older(&(a->st), a->entry)) {
				archive_entry_unset_size(a->entry);
				return (ARCHIVE_OK);
			}
		}

		/* If it's our archive, we're done. */
		if (a->skip_file_set &&
		    a->st.st_dev == (dev_t)a->skip_file_dev &&
		    a->st.st_ino == (ino_t)a->skip_file_ino) {
			archive_set_error(&a->archive, 0,
			    "Refusing to overwrite archive");
			return (ARCHIVE_FAILED);
		}

		if (!S_ISDIR(a->st.st_mode)) {
			if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
				(void)clear_nochange_fflags(a);

			if ((a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) &&
			    S_ISREG(a->st.st_mode)) {
				/* Use a temporary file to extract */
				if ((a->fd = la_mktemp(a)) == -1) {
					archive_set_error(&a->archive, errno,
					    "Can't create temporary file");
					return ARCHIVE_FAILED;
				}
				a->pst = NULL;
				en = 0;
			} else {
				/* A non-dir is in the way, unlink it. */
				if (unlink(a->name) != 0) {
					archive_set_error(&a->archive, errno,
					    "Can't unlink already-existing "
					    "object");
					return (ARCHIVE_FAILED);
				}
				a->pst = NULL;
				/* Try again. */
				en = create_filesystem_object(a);
			}
		} else if (!S_ISDIR(a->mode)) {
			/* A dir is in the way of a non-dir, rmdir it. */
			if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
				(void)clear_nochange_fflags(a);
			if (rmdir(a->name) != 0) {
				archive_set_error(&a->archive, errno,
				    "Can't replace existing directory with non-directory");
				return (ARCHIVE_FAILED);
			}
			/* Try again. */
			en = create_filesystem_object(a);
		} else {
			/*
			 * There's a dir in the way of a dir.  Don't
			 * waste time with rmdir()/mkdir(), just fix
			 * up the permissions on the existing dir.
			 * Note that we don't change perms on existing
			 * dirs unless _EXTRACT_PERM is specified.
			 */
			if ((a->mode != a->st.st_mode)
			    && (a->todo & TODO_MODE_FORCE))
				a->deferred |= (a->todo & TODO_MODE);
			/* Ownership doesn't need deferred fixup. */
			en = 0; /* Forget the EEXIST. */
		}
	}

	if (en) {
		/* Everything failed; give up here. */
		if ((&a->archive)->error == NULL)
			archive_set_error(&a->archive, en, "Can't create '%s'",
			    a->name);
		return (ARCHIVE_FAILED);
	}

	a->pst = NULL; /* Cached stat data no longer valid. */
	return (ret);
}


hfs_set_compressed_fflag(struct archive_write_disk *a)
{
	int r;

	if ((r = lazy_stat(a)) != ARCHIVE_OK)
		return (r);

	a->st.st_flags |= UF_COMPRESSED;
	if (fchflags(a->fd, a->st.st_flags) != 0) {
		archive_set_error(&a->archive, errno,
		    "Failed to set UF_COMPRESSED file flag");
		return (ARCHIVE_WARN);
	}
	return (ARCHIVE_OK);
}


set_time(int fd, int mode, const char *name,
    time_t atime, long atime_nsec,
    time_t mtime, long mtime_nsec)
{
	/* Select the best implementation for this platform. */
#if defined(HAVE_UTIMENSAT) && defined(HAVE_FUTIMENS)
	/*
	 * utimensat() and futimens() are defined in
	 * POSIX.1-2008. They support ns resolution and setting times
	 * on fds and symlinks.
	 */
	struct timespec ts[2];
	(void)mode; /* UNUSED */
	ts[0].tv_sec = atime;
	ts[0].tv_nsec = atime_nsec;
	ts[1].tv_sec = mtime;
	ts[1].tv_nsec = mtime_nsec;
	if (fd >= 0)
		return futimens(fd, ts);
	return utimensat(AT_FDCWD, name, ts, AT_SYMLINK_NOFOLLOW);

#elif HAVE_UTIMES
	/*
	 * The utimes()-family functions support µs-resolution and
	 * setting times fds and symlinks.  utimes() is documented as
	 * LEGACY by POSIX, futimes() and lutimes() are not described
	 * in POSIX.
	 */
	struct timeval times[2];

	times[0].tv_sec = atime;
	times[0].tv_usec = atime_nsec / 1000;
	times[1].tv_sec = mtime;
	times[1].tv_usec = mtime_nsec / 1000;

#ifdef HAVE_FUTIMES
	if (fd >= 0)
		return (futimes(fd, times));
#else
	(void)fd; /* UNUSED */
#endif
#ifdef HAVE_LUTIMES
	(void)mode; /* UNUSED */
	return (lutimes(name, times));
#else
	if (S_ISLNK(mode))
		return (0);
	return (utimes(name, times));
#endif

#elif defined(HAVE_UTIME)
	/*
	 * utime() is POSIX-standard but only supports 1s resolution and
	 * does not support fds or symlinks.
	 */
	struct utimbuf times;
	(void)fd; /* UNUSED */
	(void)name; /* UNUSED */
	(void)atime_nsec; /* UNUSED */
	(void)mtime_nsec; /* UNUSED */
	times.actime = atime;
	times.modtime = mtime;
	if (S_ISLNK(mode))
		return (ARCHIVE_OK);
	return (utime(name, &times));

#else
	/*
	 * We don't know how to set the time on this platform.
	 */
	(void)fd; /* UNUSED */
	(void)mode; /* UNUSED */
	(void)name; /* UNUSED */
	(void)atime_nsec; /* UNUSED */
	(void)mtime_nsec; /* UNUSED */
	return (ARCHIVE_WARN);
#endif
}


check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
    int flags, int checking_linkname)
{
#if !defined(HAVE_LSTAT) && \
    !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT))
	/* Platform doesn't have lstat, so we can't look for symlinks. */
	(void)path; /* UNUSED */
	(void)error_number; /* UNUSED */
	(void)error_string; /* UNUSED */
	(void)flags; /* UNUSED */
	(void)checking_linkname; /* UNUSED */
	return (ARCHIVE_OK);
#else
	int res = ARCHIVE_OK;
	char *tail;
	char *head;
	int last;
	char c;
	int r;
	struct stat st;
	int chdir_fd;
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
	int fd;
#endif

	/* Nothing to do here if name is empty */
	if(path[0] == '\0')
	    return (ARCHIVE_OK);

	/*
	 * Guard against symlink tricks.  Reject any archive entry whose
	 * destination would be altered by a symlink.
	 *
	 * Walk the filename in chunks separated by '/'.  For each segment:
	 *  - if it doesn't exist, continue
	 *  - if it's symlink, abort or remove it
	 *  - if it's a directory and it's not the last chunk, cd into it
	 * As we go:
	 *  head points to the current (relative) path
	 *  tail points to the temporary \0 terminating the segment we're
	 *      currently examining
	 *  c holds what used to be in *tail
	 *  last is 1 if this is the last tail
	 */
	chdir_fd = la_opendirat(AT_FDCWD, ".");
	__archive_ensure_cloexec_flag(chdir_fd);
	if (chdir_fd < 0) {
		fsobj_error(a_eno, a_estr, errno,
		    "Could not open ", path);
		return (ARCHIVE_FATAL);
	}
	head = path;
	tail = path;
	last = 0;
	/* TODO: reintroduce a safe cache here? */
	/* Skip the root directory if the path is absolute. */
	if(tail == path && tail[0] == '/')
		++tail;
	/* Keep going until we've checked the entire name.
	 * head, tail, path all alias the same string, which is
	 * temporarily zeroed at tail, so be careful restoring the
	 * stashed (c=tail[0]) for error messages.
	 * Exiting the loop with break is okay; continue is not.
	 */
	while (!last) {
		/*
		 * Skip the separator we just consumed, plus any adjacent ones
		 */
		while (*tail == '/')
		    ++tail;
		/* Skip the next path element. */
		while (*tail != '\0' && *tail != '/')
			++tail;
		/* is this the last path component? */
		last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0');
		/* temporarily truncate the string here */
		c = tail[0];
		tail[0] = '\0';
		/* Check that we haven't hit a symlink. */
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
		r = fstatat(chdir_fd, head, &st, AT_SYMLINK_NOFOLLOW);
#else
		r = lstat(head, &st);
#endif
		if (r != 0) {
			tail[0] = c;
			/* We've hit a dir that doesn't exist; stop now. */
			if (errno == ENOENT) {
				break;
			} else {
				/*
				 * Treat any other error as fatal - best to be
				 * paranoid here.
				 * Note: This effectively disables deep
				 * directory support when security checks are
				 * enabled. Otherwise, very long pathnames that
				 * trigger an error here could evade the
				 * sandbox.
				 * TODO: We could do better, but it would
				 * probably require merging the symlink checks
				 * with the deep-directory editing.
				 */
				fsobj_error(a_eno, a_estr, errno,
				    "Could not stat ", path);
				res = ARCHIVE_FAILED;
				break;
			}
		} else if (S_ISDIR(st.st_mode)) {
			if (!last) {
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
				fd = la_opendirat(chdir_fd, head);
				if (fd < 0)
					r = -1;
				else {
					r = 0;
					close(chdir_fd);
					chdir_fd = fd;
				}
#else
				r = chdir(head);
#endif
				if (r != 0) {
					tail[0] = c;
					fsobj_error(a_eno, a_estr, errno,
					    "Could not chdir ", path);
					res = (ARCHIVE_FATAL);
					break;
				}
				/* Our view is now from inside this dir: */
				head = tail + 1;
			}
		} else if (S_ISLNK(st.st_mode)) {
			if (last && checking_linkname) {
#ifdef HAVE_LINKAT
				/*
				 * Hardlinks to symlinks are safe to write
				 * if linkat() is supported as it does not
				 * follow symlinks.
				 */
				res = ARCHIVE_OK;
#else
				/*
				 * We return ARCHIVE_FAILED here as we are
				 * not able to safely write hardlinks
				 * to symlinks.
				 */
				tail[0] = c;
				fsobj_error(a_eno, a_estr, errno,
				    "Cannot write hardlink to symlink ",
				    path);
				res = ARCHIVE_FAILED;
#endif
				break;
			} else
			if (last) {
				/*
				 * Last element is symlink; remove it
				 * so we can overwrite it with the
				 * item being extracted.
				 */
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
				r = unlinkat(chdir_fd, head, 0);
#else
				r = unlink(head);
#endif
				if (r != 0) {
					tail[0] = c;
					fsobj_error(a_eno, a_estr, errno,
					    "Could not remove symlink ",
					    path);
					res = ARCHIVE_FAILED;
					break;
				}
				/*
				 * Even if we did remove it, a warning
				 * is in order.  The warning is silly,
				 * though, if we're just replacing one
				 * symlink with another symlink.
				 */
				tail[0] = c;
				/*
				 * FIXME:  not sure how important this is to
				 * restore
				 */
				/*
				if (!S_ISLNK(path)) {
					fsobj_error(a_eno, a_estr, 0,
					    "Removing symlink ", path);
				}
				*/
				/* Symlink gone.  No more problem! */
				res = ARCHIVE_OK;
				break;
			} else if (flags & ARCHIVE_EXTRACT_UNLINK) {
				/* User asked us to remove problems. */
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
				r = unlinkat(chdir_fd, head, 0);
#else
				r = unlink(head);
#endif
				if (r != 0) {
					tail[0] = c;
					fsobj_error(a_eno, a_estr, 0,
					    "Cannot remove intervening "
					    "symlink ", path);
					res = ARCHIVE_FAILED;
					break;
				}
				tail[0] = c;
			} else if ((flags &
			    ARCHIVE_EXTRACT_SECURE_SYMLINKS) == 0) {
				/*
				 * We are not the last element and we want to
				 * follow symlinks if they are a directory.
				 *
				 * This is needed to extract hardlinks over
				 * symlinks.
				 */
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
				r = fstatat(chdir_fd, head, &st, 0);
#else
				r = la_stat(head, &st);
#endif
				if (r != 0) {
					tail[0] = c;
					if (errno == ENOENT) {
						break;
					} else {
						fsobj_error(a_eno, a_estr,
						    errno,
						    "Could not stat ", path);
						res = (ARCHIVE_FAILED);
						break;
					}
				} else if (S_ISDIR(st.st_mode)) {
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
					fd = la_opendirat(chdir_fd, head);
					if (fd < 0)
						r = -1;
					else {
						r = 0;
						close(chdir_fd);
						chdir_fd = fd;
					}
#else
					r = chdir(head);
#endif
					if (r != 0) {
						tail[0] = c;
						fsobj_error(a_eno, a_estr,
						    errno,
						    "Could not chdir ", path);
						res = (ARCHIVE_FATAL);
						break;
					}
					/*
					 * Our view is now from inside
					 * this dir:
					 */
					head = tail + 1;
				} else {
					tail[0] = c;
					fsobj_error(a_eno, a_estr, 0,
					    "Cannot extract through "
					    "symlink ", path);
					res = ARCHIVE_FAILED;
					break;
				}
			} else {
				tail[0] = c;
				fsobj_error(a_eno, a_estr, 0,
				    "Cannot extract through symlink ", path);
				res = ARCHIVE_FAILED;
				break;
			}
		}
		/* be sure to always maintain this */
		tail[0] = c;
		if (tail[0] != '\0')
			tail++; /* Advance to the next segment. */
	}
	/* Catches loop exits via break */
	tail[0] = c;
#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)
	/* If we operate with openat(), fstatat() and unlinkat() there was
	 * no chdir(), so just close the fd */
	if (chdir_fd >= 0)
		close(chdir_fd);
#elif HAVE_FCHDIR
	/* If we changed directory above, restore it here. */
	if (chdir_fd >= 0) {
		r = fchdir(chdir_fd);
		if (r != 0) {
			fsobj_error(a_eno, a_estr, errno,
			    "chdir() failure", "");
		}
		close(chdir_fd);
		chdir_fd = -1;
		if (r != 0) {
			res = (ARCHIVE_FATAL);
		}
	}
#endif
	/* TODO: reintroduce a safe cache here? */
	return res;
#endif
}


hfs_write_resource_fork(struct archive_write_disk *a, unsigned char *buff,
    size_t bytes, uint32_t position)
{
	int ret;

	ret = fsetxattr(a->fd, XATTR_RESOURCEFORK_NAME, buff, bytes,
	    position, a->rsrc_xattr_options);
	if (ret < 0) {
		archive_set_error(&a->archive, errno,
		    "Cannot restore xattr: %s at %u pos %u bytes",
		    XATTR_RESOURCEFORK_NAME,
		    (unsigned)position,
		    (unsigned)bytes);
		return (ARCHIVE_WARN);
	}
	a->rsrc_xattr_options &= ~XATTR_CREATE;
	return (ARCHIVE_OK);
}


clear_nochange_fflags(struct archive_write_disk *a)
{
	mode_t		mode = archive_entry_mode(a->entry);
	const int nochange_flags = 0
#ifdef SF_IMMUTABLE
	    | SF_IMMUTABLE
#endif
#ifdef UF_IMMUTABLE
	    | UF_IMMUTABLE
#endif
#ifdef SF_APPEND
	    | SF_APPEND
#endif
#ifdef UF_APPEND
	    | UF_APPEND
#endif
#ifdef EXT2_APPEND_FL
	    | EXT2_APPEND_FL
#endif
#ifdef EXT2_IMMUTABLE_FL
	    | EXT2_IMMUTABLE_FL
#endif
	;

	return (set_fflags_platform(a, a->fd, a->name, mode, 0,
	    nochange_flags));
}


hfs_reset_compressor(struct archive_write_disk *a)
{
	int ret;

	if (a->stream_valid)
		ret = deflateReset(&a->stream);
	else
		ret = deflateInit(&a->stream, a->decmpfs_compression_level);

	if (ret != Z_OK) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Failed to initialize compressor");
		return (ARCHIVE_FATAL);
	} else
		a->stream_valid = 1;

	return (ARCHIVE_OK);
}


lazy_stat(struct archive_write_disk *a)
{
	if (a->pst != NULL) {
		/* Already have stat() data available. */
		return (ARCHIVE_OK);
	}
#ifdef HAVE_FSTAT
	if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) {
		a->pst = &a->st;
		return (ARCHIVE_OK);
	}
#endif
	/*
	 * XXX At this point, symlinks should not be hit, otherwise
	 * XXX a race occurred.  Do we want to check explicitly for that?
	 */
	if (lstat(a->name, &a->st) == 0) {
		a->pst = &a->st;
		return (ARCHIVE_OK);
	}
	archive_set_error(&a->archive, errno, "Couldn't stat file");
	return (ARCHIVE_WARN);
}


set_mode(struct archive_write_disk *a, int mode)
{
	int r = ARCHIVE_OK;
	int r2;
	mode &= 07777; /* Strip off file type bits. */

	if (a->todo & TODO_SGID_CHECK) {
		/*
		 * If we don't know the GID is right, we must stat()
		 * to verify it.  We can't just check the GID of this
		 * process, since systems sometimes set GID from
		 * the enclosing dir or based on ACLs.
		 */
		if ((r = lazy_stat(a)) != ARCHIVE_OK)
			return (r);
		if (a->pst->st_gid != a->gid) {
			mode &= ~ S_ISGID;
			if (a->flags & ARCHIVE_EXTRACT_OWNER) {
				/*
				 * This is only an error if you
				 * requested owner restore.  If you
				 * didn't, we'll try to restore
				 * sgid/suid, but won't consider it a
				 * problem if we can't.
				 */
				archive_set_error(&a->archive, -1,
				    "Can't restore SGID bit");
				r = ARCHIVE_WARN;
			}
		}
		/* While we're here, double-check the UID. */
		if (a->pst->st_uid != a->uid
		    && (a->todo & TODO_SUID)) {
			mode &= ~ S_ISUID;
			if (a->flags & ARCHIVE_EXTRACT_OWNER) {
				archive_set_error(&a->archive, -1,
				    "Can't restore SUID bit");
				r = ARCHIVE_WARN;
			}
		}
		a->todo &= ~TODO_SGID_CHECK;
		a->todo &= ~TODO_SUID_CHECK;
	} else if (a->todo & TODO_SUID_CHECK) {
		/*
		 * If we don't know the UID is right, we can just check
		 * the user, since all systems set the file UID from
		 * the process UID.
		 */
		if (a->user_uid != a->uid) {
			mode &= ~ S_ISUID;
			if (a->flags & ARCHIVE_EXTRACT_OWNER) {
				archive_set_error(&a->archive, -1,
				    "Can't make file SUID");
				r = ARCHIVE_WARN;
			}
		}
		a->todo &= ~TODO_SUID_CHECK;
	}

	if (S_ISLNK(a->mode)) {
#ifdef HAVE_LCHMOD
		/*
		 * If this is a symlink, use lchmod().  If the
		 * platform doesn't support lchmod(), just skip it.  A
		 * platform that doesn't provide a way to set
		 * permissions on symlinks probably ignores
		 * permissions on symlinks, so a failure here has no
		 * impact.
		 */
		if (lchmod(a->name, mode) != 0) {
			switch (errno) {
			case ENOTSUP:
			case ENOSYS:
#if ENOTSUP != EOPNOTSUPP
			case EOPNOTSUPP:
#endif
				/*
				 * if lchmod is defined but the platform
				 * doesn't support it, silently ignore
				 * error
				 */
				break;
			default:
				archive_set_error(&a->archive, errno,
				    "Can't set permissions to 0%o", (int)mode);
				r = ARCHIVE_WARN;
			}
		}
#endif
	} else if (!S_ISDIR(a->mode)) {
		/*
		 * If it's not a symlink and not a dir, then use
		 * fchmod() or chmod(), depending on whether we have
		 * an fd.  Dirs get their perms set during the
		 * post-extract fixup, which is handled elsewhere.
		 */
#ifdef HAVE_FCHMOD
		if (a->fd >= 0)
			r2 = fchmod(a->fd, mode);
		else
#endif
		/* If this platform lacks fchmod(), then
		 * we'll just use chmod(). */
		r2 = chmod(a->name, mode);

		if (r2 != 0) {
			archive_set_error(&a->archive, errno,
			    "Can't set permissions to 0%o", (int)mode);
			r = ARCHIVE_WARN;
		}
	}
	return (r);
}


set_mac_metadata(struct archive_write_disk *a, const char *pathname,
		 const void *metadata, size_t metadata_size)
{
	(void)a; /* UNUSED */
	(void)pathname; /* UNUSED */
	(void)metadata; /* UNUSED */
	(void)metadata_size; /* UNUSED */
	return (ARCHIVE_OK);
}


fsobj_error(int *a_eno, struct archive_string *a_estr,
    int err, const char *errstr, const char *path)
{
	if (a_eno)
		*a_eno = err;
	if (a_estr)
		archive_string_sprintf(a_estr, "%s%s", errstr, path);
}


hfs_write_decmpfs_block(struct archive_write_disk *a, const char *buff,
    size_t size)
{
	const char *buffer_to_write;
	size_t bytes_to_write;
	int ret;

	if (a->decmpfs_block_count == (unsigned)-1) {
		void *new_block;
		size_t new_size;
		unsigned int block_count;

		if (a->decmpfs_header_p == NULL) {
			new_block = malloc(MAX_DECMPFS_XATTR_SIZE
			    + sizeof(uint32_t));
			if (new_block == NULL) {
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory for decmpfs");
				return (ARCHIVE_FATAL);
			}
			a->decmpfs_header_p = new_block;
		}
		a->decmpfs_attr_size = DECMPFS_HEADER_SIZE;
		archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_MAGIC],
		    DECMPFS_MAGIC);
		archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_TYPE],
		    CMP_RESOURCE_FORK);
		archive_le64enc(&a->decmpfs_header_p[DECMPFS_UNCOMPRESSED_SIZE],
		    a->filesize);

		/* Calculate a block count of the file. */
		block_count =
		    (a->filesize + MAX_DECMPFS_BLOCK_SIZE -1) /
			MAX_DECMPFS_BLOCK_SIZE;
		/*
		 * Allocate buffer for resource fork.
		 * Set up related pointers;
		 */
		new_size =
		    RSRC_H_SIZE + /* header */
		    4 + /* Block count */
		    (block_count * sizeof(uint32_t) * 2) +
		    RSRC_F_SIZE; /* footer */
		if (new_size > a->resource_fork_allocated_size) {
			new_block = realloc(a->resource_fork, new_size);
			if (new_block == NULL) {
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory for ResourceFork");
				return (ARCHIVE_FATAL);
			}
			a->resource_fork_allocated_size = new_size;
			a->resource_fork = new_block;
		}

		/* Allocate uncompressed buffer */
		if (a->uncompressed_buffer == NULL) {
			new_block = malloc(MAX_DECMPFS_BLOCK_SIZE);
			if (new_block == NULL) {
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory for decmpfs");
				return (ARCHIVE_FATAL);
			}
			a->uncompressed_buffer = new_block;
		}
		a->block_remaining_bytes = MAX_DECMPFS_BLOCK_SIZE;
		a->file_remaining_bytes = a->filesize;
		a->compressed_buffer_remaining = a->compressed_buffer_size;

		/*
		 * Set up a resource fork.
		 */
		a->rsrc_xattr_options = XATTR_CREATE;
		/* Get the position where we are going to set a bunch
		 * of block info. */
		a->decmpfs_block_info =
		    (uint32_t *)(a->resource_fork + RSRC_H_SIZE);
		/* Set the block count to the resource fork. */
		archive_le32enc(a->decmpfs_block_info++, block_count);
		/* Get the position where we are going to set compressed
		 * data. */
		a->compressed_rsrc_position =
		    RSRC_H_SIZE + 4 + (block_count * 8);
		a->compressed_rsrc_position_v = a->compressed_rsrc_position;
		a->decmpfs_block_count = block_count;
	}

	/* Ignore redundant bytes. */
	if (a->file_remaining_bytes == 0)
		return ((ssize_t)size);

	/* Do not overrun a block size. */
	if (size > a->block_remaining_bytes)
		bytes_to_write = a->block_remaining_bytes;
	else
		bytes_to_write = size;
	/* Do not overrun the file size. */
	if (bytes_to_write > a->file_remaining_bytes)
		bytes_to_write = a->file_remaining_bytes;

	/* For efficiency, if a copy length is full of the uncompressed
	 * buffer size, do not copy writing data to it. */
	if (bytes_to_write == MAX_DECMPFS_BLOCK_SIZE)
		buffer_to_write = buff;
	else {
		memcpy(a->uncompressed_buffer +
		    MAX_DECMPFS_BLOCK_SIZE - a->block_remaining_bytes,
		    buff, bytes_to_write);
		buffer_to_write = a->uncompressed_buffer;
	}
	a->block_remaining_bytes -= bytes_to_write;
	a->file_remaining_bytes -= bytes_to_write;

	if (a->block_remaining_bytes == 0 || a->file_remaining_bytes == 0) {
		ret = hfs_drive_compressor(a, buffer_to_write,
		    MAX_DECMPFS_BLOCK_SIZE - a->block_remaining_bytes);
		if (ret < 0)
			return (ret);
		a->block_remaining_bytes = MAX_DECMPFS_BLOCK_SIZE;
	}
	/* Ignore redundant bytes. */
	if (a->file_remaining_bytes == 0)
		return ((ssize_t)size);
	return (bytes_to_write);
}


hfs_drive_compressor(struct archive_write_disk *a, const char *buff,
    size_t size)
{
	unsigned char *buffer_compressed;
	size_t bytes_compressed;
	size_t bytes_used;
	int ret;

	ret = hfs_reset_compressor(a);
	if (ret != ARCHIVE_OK)
		return (ret);

	if (a->compressed_buffer == NULL) {
		size_t block_size;

		block_size = COMPRESSED_W_SIZE + RSRC_F_SIZE +
		    + compressBound(MAX_DECMPFS_BLOCK_SIZE);
		a->compressed_buffer = malloc(block_size);
		if (a->compressed_buffer == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory for Resource Fork");
			return (ARCHIVE_FATAL);
		}
		a->compressed_buffer_size = block_size;
		a->compressed_buffer_remaining = block_size;
	}

	buffer_compressed = a->compressed_buffer +
	    a->compressed_buffer_size - a->compressed_buffer_remaining;
	a->stream.next_in = (Bytef *)(uintptr_t)(const void *)buff;
	a->stream.avail_in = size;
	a->stream.next_out = buffer_compressed;
	a->stream.avail_out = a->compressed_buffer_remaining;
	do {
		ret = deflate(&a->stream, Z_FINISH);
		switch (ret) {
		case Z_OK:
		case Z_STREAM_END:
			break;
		default:
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Failed to compress data");
			return (ARCHIVE_FAILED);
		}
	} while (ret == Z_OK);
	bytes_compressed = a->compressed_buffer_remaining - a->stream.avail_out;

	/*
	 * If the compressed size is larger than the original size,
	 * throw away compressed data, use uncompressed data instead.
	 */
	if (bytes_compressed > size) {
		buffer_compressed[0] = 0xFF;/* uncompressed marker. */
		memcpy(buffer_compressed + 1, buff, size);
		bytes_compressed = size + 1;
	}
	a->compressed_buffer_remaining -= bytes_compressed;

	/*
	 * If the compressed size is smaller than MAX_DECMPFS_XATTR_SIZE
	 * and the block count in the file is only one, store compressed
	 * data to decmpfs xattr instead of the resource fork.
	 */
	if (a->decmpfs_block_count == 1 &&
	    (a->decmpfs_attr_size + bytes_compressed)
	      <= MAX_DECMPFS_XATTR_SIZE) {
		archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_TYPE],
		    CMP_XATTR);
		memcpy(a->decmpfs_header_p + DECMPFS_HEADER_SIZE,
		    buffer_compressed, bytes_compressed);
		a->decmpfs_attr_size += bytes_compressed;
		a->compressed_buffer_remaining = a->compressed_buffer_size;
		/*
		 * Finish HFS+ Compression.
		 * - Write the decmpfs xattr.
		 * - Set the UF_COMPRESSED file flag.
		 */
		ret = hfs_write_decmpfs(a);
		if (ret == ARCHIVE_OK)
			ret = hfs_set_compressed_fflag(a);
		return (ret);
	}

	/* Update block info. */
	archive_le32enc(a->decmpfs_block_info++,
	    a->compressed_rsrc_position_v - RSRC_H_SIZE);
	archive_le32enc(a->decmpfs_block_info++, bytes_compressed);
	a->compressed_rsrc_position_v += bytes_compressed;

	/*
	 * Write the compressed data to the resource fork.
	 */
	bytes_used = a->compressed_buffer_size - a->compressed_buffer_remaining;
	while (bytes_used >= COMPRESSED_W_SIZE) {
		ret = hfs_write_compressed_data(a, COMPRESSED_W_SIZE);
		if (ret != ARCHIVE_OK)
			return (ret);
		bytes_used -= COMPRESSED_W_SIZE;
		if (bytes_used > COMPRESSED_W_SIZE)
			memmove(a->compressed_buffer,
			    a->compressed_buffer + COMPRESSED_W_SIZE,
			    bytes_used);
		else
			memcpy(a->compressed_buffer,
			    a->compressed_buffer + COMPRESSED_W_SIZE,
			    bytes_used);
	}
	a->compressed_buffer_remaining = a->compressed_buffer_size - bytes_used;

	/*
	 * If the current block is the last block, write the remaining
	 * compressed data and the resource fork footer.
	 */
	if (a->file_remaining_bytes == 0) {
		size_t rsrc_size;
		int64_t bk;

		/* Append the resource footer. */
		rsrc_size = hfs_set_resource_fork_footer(
		    a->compressed_buffer + bytes_used,
		    a->compressed_buffer_remaining);
		ret = hfs_write_compressed_data(a, bytes_used + rsrc_size);
		a->compressed_buffer_remaining = a->compressed_buffer_size;

		/* If the compressed size is not enough smaller than
		 * the uncompressed size. cancel HFS+ compression.
		 * TODO: study a behavior of ditto utility and improve
		 * the condition to fall back into no HFS+ compression. */
		bk = HFS_BLOCKS(a->compressed_rsrc_position);
		bk += bk >> 7;
		if (bk > HFS_BLOCKS(a->filesize))
			return hfs_decompress(a);
		/*
		 * Write the resourcefork header.
		 */
		if (ret == ARCHIVE_OK)
			ret = hfs_write_resource_fork_header(a);
		/*
		 * Finish HFS+ Compression.
		 * - Write the decmpfs xattr.
		 * - Set the UF_COMPRESSED file flag.
		 */
		if (ret == ARCHIVE_OK)
			ret = hfs_write_decmpfs(a);
		if (ret == ARCHIVE_OK)
			ret = hfs_set_compressed_fflag(a);
	}
	return (ret);
}


hfs_write_data_block(struct archive_write_disk *a, const char *buff,
    size_t size)
{
	return (write_data_block(a, buff, size));
}


media: vidtv: Check for null return of vzalloc

As the possible failure of the vzalloc(), e->encoder_buf might be NULL.
Therefore, it should be better to check it in order
to guarantee the success of the initialization.
If fails, we need to free not only 'e' but also 'e->name'.
Also, if the allocation for ctx fails, we need to free 'e->encoder_buf'
else.

Fixes: f90cf6079bf6 ("media: vidtv: add a bridge driver")
Signed-off-by: Jiasheng Jiang <jiasheng@iscas.ac.cn>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>

static void vidtv_s302m_alloc_au(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *sync_au = NULL;
	struct vidtv_access_unit *temp = NULL;

	if (e->sync && e->sync->is_video_encoder) {
		sync_au = e->sync->access_units;

		while (sync_au) {
			temp = vidtv_s302m_access_unit_init(e->access_units);
			if (!e->access_units)
				e->access_units = temp;

			sync_au = sync_au->next;
		}

		return;
	}

	e->access_units = vidtv_s302m_access_unit_init(NULL);
}

An issue was discovered in the Linux kernel through 5.16-rc6. There is a lack of check after calling vzalloc() and lack of free after allocation in drivers/media/test-drivers/vidtv/vidtv_s302m.c.


static struct vidtv_access_unit *vidtv_s302m_access_unit_init(struct vidtv_access_unit *head)
{
	struct vidtv_access_unit *au;

	au = kzalloc(sizeof(*au), GFP_KERNEL);
	if (!au)
		return NULL;

	if (head) {
		while (head->next)
			head = head->next;

		head->next = au;
	}

	return au;
}


static void *vidtv_s302m_encode(struct vidtv_encoder *e)
{
	struct vidtv_s302m_ctx *ctx = e->ctx;

	/*
	 * According to SMPTE 302M, an audio access unit is specified as those
	 * AES3 words that are associated with a corresponding video frame.
	 * Therefore, there is one audio access unit for every video access unit
	 * in the corresponding video encoder ('sync'), using the same values
	 * for PTS as used by the video encoder.
	 *
	 * Assuming that it is also possible to send audio without any
	 * associated video, as in a radio-like service, a single audio access unit
	 * is created with values for 'num_samples' and 'pts' taken empirically from
	 * ffmpeg
	 */

	vidtv_s302m_access_unit_destroy(e);
	vidtv_s302m_alloc_au(e);

	if (e->sync && e->sync->is_video_encoder) {
		vidtv_s302m_compute_sample_count_from_video(e);
		vidtv_s302m_compute_pts_from_video(e);
	} else {
		e->access_units->num_samples = FF_S302M_DEFAULT_NUM_FRAMES;
		e->access_units->pts = (ctx->au_count * FF_S302M_DEFAULT_PTS_INCREMENT) +
				       FF_S302M_DEFAULT_PTS_OFFSET;
	}

	vidtv_s302m_write_frames(e);

	return e->encoder_buf;
}


*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args)
{
	u32 priv_sz = sizeof(struct vidtv_s302m_ctx);
	struct vidtv_s302m_ctx *ctx;
	struct vidtv_encoder *e;

	e = kzalloc(sizeof(*e), GFP_KERNEL);
	if (!e)
		return NULL;

	e->id = S302M;

	if (args.name)
		e->name = kstrdup(args.name, GFP_KERNEL);

	e->encoder_buf = vzalloc(VIDTV_S302M_BUF_SZ);
	if (!e->encoder_buf)
		goto out_kfree_e;

	e->encoder_buf_sz = VIDTV_S302M_BUF_SZ;
	e->encoder_buf_offset = 0;

	e->sample_count = 0;

	e->src_buf = (args.src_buf) ? args.src_buf : NULL;
	e->src_buf_sz = (args.src_buf) ? args.src_buf_sz : 0;
	e->src_buf_offset = 0;

	e->is_video_encoder = false;

	ctx = kzalloc(priv_sz, GFP_KERNEL);
	if (!ctx)
		goto out_kfree_buf;

	e->ctx = ctx;
	ctx->last_duration = 0;

	e->encode = vidtv_s302m_encode;
	e->clear = vidtv_s302m_clear;

	e->es_pid = cpu_to_be16(args.es_pid);
	e->stream_id = cpu_to_be16(PES_PRIVATE_STREAM_1);

	e->sync = args.sync;
	e->sampling_rate_hz = S302M_SAMPLING_RATE_HZ;

	e->last_sample_cb = args.last_sample_cb;

	e->destroy = vidtv_s302m_encoder_destroy;

	if (args.head) {
		while (args.head->next)
			args.head = args.head->next;

		args.head->next = e;
	}

	e->next = NULL;

	return e;

out_kfree_buf:
	kfree(e->encoder_buf);

out_kfree_e:
	kfree(e->name);
	kfree(e);
	return NULL;
}


static void vidtv_s302m_write_frames(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *au = e->access_units;
	struct vidtv_s302m_ctx *ctx = e->ctx;
	u32 nbytes_per_unit = 0;
	u32 nbytes = 0;
	u32 au_sz = 0;
	u16 sample;
	u32 j;

	while (au) {
		au_sz = au->num_samples *
			sizeof(struct vidtv_s302m_frame_16);

		nbytes_per_unit = vidtv_s302m_write_h(e, au_sz);

		for (j = 0; j < au->num_samples; ++j) {
			sample = vidtv_s302m_get_sample(e);
			nbytes_per_unit += vidtv_s302m_write_frame(e, sample);

			if (e->src_buf)
				e->src_buf_offset += sizeof(u16);

			e->sample_count++;
		}

		au->nbytes = nbytes_per_unit;

		if (au_sz + sizeof(struct vidtv_smpte_s302m_es) != nbytes_per_unit) {
			pr_warn_ratelimited("write size was %u, expected %zu\n",
					    nbytes_per_unit,
					    au_sz + sizeof(struct vidtv_smpte_s302m_es));
		}

		nbytes += nbytes_per_unit;
		au->offset = nbytes - nbytes_per_unit;

		nbytes_per_unit = 0;
		ctx->au_count++;

		au = au->next;
	}
}


static u16 vidtv_s302m_get_sample(struct vidtv_encoder *e)
{
	u16 sample;
	int pos;
	struct vidtv_s302m_ctx *ctx = e->ctx;

	if (!e->src_buf) {
		/*
		 * Simple tone generator: play the tones at the
		 * beethoven_fur_elise array.
		 */
		if (ctx->last_duration <= 0) {
			if (e->src_buf_offset >= ARRAY_SIZE(beethoven_fur_elise))
				e->src_buf_offset = 0;

			ctx->last_tone = beethoven_fur_elise[e->src_buf_offset].note;
			ctx->last_duration = beethoven_fur_elise[e->src_buf_offset].duration *
					     S302M_SAMPLING_RATE_HZ / COMPASS / 5;
			e->src_buf_offset++;
			ctx->note_offset = 0;
		} else {
			ctx->last_duration--;
		}

		/* Handle pause notes */
		if (!ctx->last_tone)
			return 0x8000;

		pos = (2 * PI * ctx->note_offset * ctx->last_tone) / S302M_SAMPLING_RATE_HZ;
		ctx->note_offset++;

		return (fixp_sin32(pos % (2 * PI)) >> 16) + 0x8000;
	}

	/* bug somewhere */
	if (e->src_buf_offset > e->src_buf_sz) {
		pr_err_ratelimited("overflow detected: %d > %d, wrapping.\n",
				   e->src_buf_offset,
				   e->src_buf_sz);

		e->src_buf_offset = 0;
	}

	if (e->src_buf_offset >= e->src_buf_sz) {
		/* let the source know we are out of data */
		if (e->last_sample_cb)
			e->last_sample_cb(e->sample_count);

		e->src_buf_offset = 0;
	}

	sample = *(u16 *)(e->src_buf + e->src_buf_offset);

	return sample;
}


static u32 vidtv_s302m_write_h(struct vidtv_encoder *e, u32 p_sz)
{
	struct vidtv_smpte_s302m_es h = {};
	u32 nbytes = 0;

	/* 2 channels, ident: 0, 16 bits per sample */
	h.bitfield = cpu_to_be32((p_sz << 16));

	nbytes += vidtv_memcpy(e->encoder_buf,
			       e->encoder_buf_offset,
			       e->encoder_buf_sz,
			       &h,
			       sizeof(h));

	e->encoder_buf_offset += nbytes;
	return nbytes;
}


static void vidtv_s302m_access_unit_destroy(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *head = e->access_units;
	struct vidtv_access_unit *tmp = NULL;

	while (head) {
		tmp = head;
		head = head->next;
		kfree(tmp);
	}

	e->access_units = NULL;
}


static u32 vidtv_s302m_clear(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *au = e->access_units;
	u32 count = 0;

	while (au) {
		count++;
		au = au->next;
	}

	vidtv_s302m_access_unit_destroy(e);
	memset(e->encoder_buf, 0, VIDTV_S302M_BUF_SZ);
	e->encoder_buf_offset = 0;

	return count;
}


void vidtv_s302m_encoder_destroy(struct vidtv_encoder *e)
{
	if (e->id != S302M) {
		pr_err_ratelimited("Encoder type mismatch, skipping.\n");
		return;
	}

	vidtv_s302m_access_unit_destroy(e);
	kfree(e->name);
	vfree(e->encoder_buf);
	kfree(e->ctx);
	kfree(e);
}


static u32 vidtv_s302m_write_frame(struct vidtv_encoder *e,
				   u16 sample)
{
	struct vidtv_s302m_ctx *ctx = e->ctx;
	struct vidtv_s302m_frame_16 f = {};
	u32 nbytes = 0;

	/* from ffmpeg: see s302enc.c */

	u8 vucf = ctx->frame_index == 0 ? 0x10 : 0;

	f.data[0] = sample & 0xFF;
	f.data[1] = (sample & 0xFF00) >>  8;
	f.data[2] = ((sample & 0x0F)  <<  4) | vucf;
	f.data[3] = (sample & 0x0FF0) >>  4;
	f.data[4] = (sample & 0xF000) >> 12;

	f.data[0] = reverse[f.data[0]];
	f.data[1] = reverse[f.data[1]];
	f.data[2] = reverse[f.data[2]];
	f.data[3] = reverse[f.data[3]];
	f.data[4] = reverse[f.data[4]];

	nbytes += vidtv_memcpy(e->encoder_buf,
			       e->encoder_buf_offset,
			       VIDTV_S302M_BUF_SZ,
			       &f,
			       sizeof(f));

	e->encoder_buf_offset += nbytes;

	ctx->frame_index++;
	if (ctx->frame_index >= S302M_BLOCK_SZ)
		ctx->frame_index = 0;

	return nbytes;
}


vidtv_s302m_compute_sample_count_from_video(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *sync_au = e->sync->access_units;
	struct vidtv_access_unit *au = e->access_units;
	u32 sample_duration_usecs;
	u32 vau_duration_usecs;
	u32 s;

	vau_duration_usecs    = USEC_PER_SEC / e->sync->sampling_rate_hz;
	sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz;

	while (au && sync_au) {
		s = DIV_ROUND_UP(vau_duration_usecs, sample_duration_usecs);
		au->num_samples = s;
		au = au->next;
		sync_au = sync_au->next;
	}
}


static void vidtv_s302m_compute_pts_from_video(struct vidtv_encoder *e)
{
	struct vidtv_access_unit *au = e->access_units;
	struct vidtv_access_unit *sync_au = e->sync->access_units;

	/* use the same pts from the video access unit*/
	while (au && sync_au) {
		au->pts = sync_au->pts;
		au = au->next;
		sync_au = sync_au->next;
	}
}


patch 9.0.0011: reading beyond the end of the line with put command

Problem:    Reading beyond the end of the line with put command.
Solution:   Adjust the end mark position.

do_put(
    int		regname,
    char_u	*expr_result,	// result for regname "=" when compiled
    int		dir,		// BACKWARD for 'P', FORWARD for 'p'
    long	count,
    int		flags)
{
    char_u	*ptr;
    char_u	*newp, *oldp;
    int		yanklen;
    int		totlen = 0;		// init for gcc
    linenr_T	lnum;
    colnr_T	col;
    long	i;			// index in y_array[]
    int		y_type;
    long	y_size;
    int		oldlen;
    long	y_width = 0;
    colnr_T	vcol;
    int		delcount;
    int		incr = 0;
    long	j;
    struct block_def bd;
    char_u	**y_array = NULL;
    yankreg_T	*y_current_used = NULL;
    long	nr_lines = 0;
    pos_T	new_cursor;
    int		indent;
    int		orig_indent = 0;	// init for gcc
    int		indent_diff = 0;	// init for gcc
    int		first_indent = TRUE;
    int		lendiff = 0;
    pos_T	old_pos;
    char_u	*insert_string = NULL;
    int		allocated = FALSE;
    long	cnt;
    pos_T	orig_start = curbuf->b_op_start;
    pos_T	orig_end = curbuf->b_op_end;
    unsigned int cur_ve_flags = get_ve_flags();

#ifdef FEAT_CLIPBOARD
    // Adjust register name for "unnamed" in 'clipboard'.
    adjust_clip_reg(&regname);
    (void)may_get_selection(regname);
#endif

    if (flags & PUT_FIXINDENT)
	orig_indent = get_indent();

    curbuf->b_op_start = curwin->w_cursor;	// default for '[ mark
    curbuf->b_op_end = curwin->w_cursor;	// default for '] mark

    // Using inserted text works differently, because the register includes
    // special characters (newlines, etc.).
    if (regname == '.')
    {
	if (VIsual_active)
	    stuffcharReadbuff(VIsual_mode);
	(void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') :
				    (count == -1 ? 'O' : 'i')), count, FALSE);
	// Putting the text is done later, so can't really move the cursor to
	// the next character.  Use "l" to simulate it.
	if ((flags & PUT_CURSEND) && gchar_cursor() != NUL)
	    stuffcharReadbuff('l');
	return;
    }

    // For special registers '%' (file name), '#' (alternate file name) and
    // ':' (last command line), etc. we have to create a fake yank register.
    // For compiled code "expr_result" holds the expression result.
    if (regname == '=' && expr_result != NULL)
	insert_string = expr_result;
    else if (get_spec_reg(regname, &insert_string, &allocated, TRUE)
		&& insert_string == NULL)
	return;

    // Autocommands may be executed when saving lines for undo.  This might
    // make "y_array" invalid, so we start undo now to avoid that.
    if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL)
	goto end;

    if (insert_string != NULL)
    {
	y_type = MCHAR;
#ifdef FEAT_EVAL
	if (regname == '=')
	{
	    // For the = register we need to split the string at NL
	    // characters.
	    // Loop twice: count the number of lines and save them.
	    for (;;)
	    {
		y_size = 0;
		ptr = insert_string;
		while (ptr != NULL)
		{
		    if (y_array != NULL)
			y_array[y_size] = ptr;
		    ++y_size;
		    ptr = vim_strchr(ptr, '\n');
		    if (ptr != NULL)
		    {
			if (y_array != NULL)
			    *ptr = NUL;
			++ptr;
			// A trailing '\n' makes the register linewise.
			if (*ptr == NUL)
			{
			    y_type = MLINE;
			    break;
			}
		    }
		}
		if (y_array != NULL)
		    break;
		y_array = ALLOC_MULT(char_u *, y_size);
		if (y_array == NULL)
		    goto end;
	    }
	}
	else
#endif
	{
	    y_size = 1;		// use fake one-line yank register
	    y_array = &insert_string;
	}
    }
    else
    {
	get_yank_register(regname, FALSE);

	y_type = y_current->y_type;
	y_width = y_current->y_width;
	y_size = y_current->y_size;
	y_array = y_current->y_array;
	y_current_used = y_current;
    }

    if (y_type == MLINE)
    {
	if (flags & PUT_LINE_SPLIT)
	{
	    char_u *p;

	    // "p" or "P" in Visual mode: split the lines to put the text in
	    // between.
	    if (u_save_cursor() == FAIL)
		goto end;
	    p = ml_get_cursor();
	    if (dir == FORWARD && *p != NUL)
		MB_PTR_ADV(p);
	    ptr = vim_strsave(p);
	    if (ptr == NULL)
		goto end;
	    ml_append(curwin->w_cursor.lnum, ptr, (colnr_T)0, FALSE);
	    vim_free(ptr);

	    oldp = ml_get_curline();
	    p = oldp + curwin->w_cursor.col;
	    if (dir == FORWARD && *p != NUL)
		MB_PTR_ADV(p);
	    ptr = vim_strnsave(oldp, p - oldp);
	    if (ptr == NULL)
		goto end;
	    ml_replace(curwin->w_cursor.lnum, ptr, FALSE);
	    ++nr_lines;
	    dir = FORWARD;
	}
	if (flags & PUT_LINE_FORWARD)
	{
	    // Must be "p" for a Visual block, put lines below the block.
	    curwin->w_cursor = curbuf->b_visual.vi_end;
	    dir = FORWARD;
	}
	curbuf->b_op_start = curwin->w_cursor;	// default for '[ mark
	curbuf->b_op_end = curwin->w_cursor;	// default for '] mark
    }

    if (flags & PUT_LINE)	// :put command or "p" in Visual line mode.
	y_type = MLINE;

    if (y_size == 0 || y_array == NULL)
    {
	semsg(_(e_nothing_in_register_str),
		  regname == 0 ? (char_u *)"\"" : transchar(regname));
	goto end;
    }

    if (y_type == MBLOCK)
    {
	lnum = curwin->w_cursor.lnum + y_size + 1;
	if (lnum > curbuf->b_ml.ml_line_count)
	    lnum = curbuf->b_ml.ml_line_count + 1;
	if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL)
	    goto end;
    }
    else if (y_type == MLINE)
    {
	lnum = curwin->w_cursor.lnum;
#ifdef FEAT_FOLDING
	// Correct line number for closed fold.  Don't move the cursor yet,
	// u_save() uses it.
	if (dir == BACKWARD)
	    (void)hasFolding(lnum, &lnum, NULL);
	else
	    (void)hasFolding(lnum, NULL, &lnum);
#endif
	if (dir == FORWARD)
	    ++lnum;
	// In an empty buffer the empty line is going to be replaced, include
	// it in the saved lines.
	if ((BUFEMPTY() ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL)
	    goto end;
#ifdef FEAT_FOLDING
	if (dir == FORWARD)
	    curwin->w_cursor.lnum = lnum - 1;
	else
	    curwin->w_cursor.lnum = lnum;
	curbuf->b_op_start = curwin->w_cursor;	// for mark_adjust()
#endif
    }
    else if (u_save_cursor() == FAIL)
	goto end;

    yanklen = (int)STRLEN(y_array[0]);

    if (cur_ve_flags == VE_ALL && y_type == MCHAR)
    {
	if (gchar_cursor() == TAB)
	{
	    int viscol = getviscol();
	    int ts = curbuf->b_p_ts;

	    // Don't need to insert spaces when "p" on the last position of a
	    // tab or "P" on the first position.
	    if (dir == FORWARD ?
#ifdef FEAT_VARTABS
		    tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1
#else
		    ts - (viscol % ts) != 1
#endif
		    : curwin->w_cursor.coladd > 0)
		coladvance_force(viscol);
	    else
		curwin->w_cursor.coladd = 0;
	}
	else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL)
	    coladvance_force(getviscol() + (dir == FORWARD));
    }

    lnum = curwin->w_cursor.lnum;
    col = curwin->w_cursor.col;

    // Block mode
    if (y_type == MBLOCK)
    {
	int	c = gchar_cursor();
	colnr_T	endcol2 = 0;

	if (dir == FORWARD && c != NUL)
	{
	    if (cur_ve_flags == VE_ALL)
		getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
	    else
		getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);

	    if (has_mbyte)
		// move to start of next multi-byte character
		curwin->w_cursor.col += (*mb_ptr2len)(ml_get_cursor());
	    else
	    if (c != TAB || cur_ve_flags != VE_ALL)
		++curwin->w_cursor.col;
	    ++col;
	}
	else
	    getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);

	col += curwin->w_cursor.coladd;
	if (cur_ve_flags == VE_ALL
		&& (curwin->w_cursor.coladd > 0
		    || endcol2 == curwin->w_cursor.col))
	{
	    if (dir == FORWARD && c == NUL)
		++col;
	    if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0)
		++curwin->w_cursor.col;
	    if (c == TAB)
	    {
		if (dir == BACKWARD && curwin->w_cursor.col)
		    curwin->w_cursor.col--;
		if (dir == FORWARD && col - 1 == endcol2)
		    curwin->w_cursor.col++;
	    }
	}
	curwin->w_cursor.coladd = 0;
	bd.textcol = 0;
	for (i = 0; i < y_size; ++i)
	{
	    int spaces = 0;
	    char shortline;

	    bd.startspaces = 0;
	    bd.endspaces = 0;
	    vcol = 0;
	    delcount = 0;

	    // add a new line
	    if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
	    {
		if (ml_append(curbuf->b_ml.ml_line_count, (char_u *)"",
						   (colnr_T)1, FALSE) == FAIL)
		    break;
		++nr_lines;
	    }
	    // get the old line and advance to the position to insert at
	    oldp = ml_get_curline();
	    oldlen = (int)STRLEN(oldp);
	    for (ptr = oldp; vcol < col && *ptr; )
	    {
		// Count a tab for what it's worth (if list mode not on)
		incr = lbr_chartabsize_adv(oldp, &ptr, vcol);
		vcol += incr;
	    }
	    bd.textcol = (colnr_T)(ptr - oldp);

	    shortline = (vcol < col) || (vcol == col && !*ptr) ;

	    if (vcol < col) // line too short, padd with spaces
		bd.startspaces = col - vcol;
	    else if (vcol > col)
	    {
		bd.endspaces = vcol - col;
		bd.startspaces = incr - bd.endspaces;
		--bd.textcol;
		delcount = 1;
		if (has_mbyte)
		    bd.textcol -= (*mb_head_off)(oldp, oldp + bd.textcol);
		if (oldp[bd.textcol] != TAB)
		{
		    // Only a Tab can be split into spaces.  Other
		    // characters will have to be moved to after the
		    // block, causing misalignment.
		    delcount = 0;
		    bd.endspaces = 0;
		}
	    }

	    yanklen = (int)STRLEN(y_array[i]);

	    if ((flags & PUT_BLOCK_INNER) == 0)
	    {
		// calculate number of spaces required to fill right side of
		// block
		spaces = y_width + 1;
		for (j = 0; j < yanklen; j++)
		    spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0);
		if (spaces < 0)
		    spaces = 0;
	    }

	    // Insert the new text.
	    // First check for multiplication overflow.
	    if (yanklen + spaces != 0
		     && count > ((INT_MAX - (bd.startspaces + bd.endspaces))
							/ (yanklen + spaces)))
	    {
		emsg(_(e_resulting_text_too_long));
		break;
	    }

	    totlen = count * (yanklen + spaces) + bd.startspaces + bd.endspaces;
	    newp = alloc(totlen + oldlen + 1);
	    if (newp == NULL)
		break;

	    // copy part up to cursor to new line
	    ptr = newp;
	    mch_memmove(ptr, oldp, (size_t)bd.textcol);
	    ptr += bd.textcol;

	    // may insert some spaces before the new text
	    vim_memset(ptr, ' ', (size_t)bd.startspaces);
	    ptr += bd.startspaces;

	    // insert the new text
	    for (j = 0; j < count; ++j)
	    {
		mch_memmove(ptr, y_array[i], (size_t)yanklen);
		ptr += yanklen;

		// insert block's trailing spaces only if there's text behind
		if ((j < count - 1 || !shortline) && spaces)
		{
		    vim_memset(ptr, ' ', (size_t)spaces);
		    ptr += spaces;
		}
		else
		    totlen -= spaces;  // didn't use these spaces
	    }

	    // may insert some spaces after the new text
	    vim_memset(ptr, ' ', (size_t)bd.endspaces);
	    ptr += bd.endspaces;

	    // move the text after the cursor to the end of the line.
	    mch_memmove(ptr, oldp + bd.textcol + delcount,
				(size_t)(oldlen - bd.textcol - delcount + 1));
	    ml_replace(curwin->w_cursor.lnum, newp, FALSE);

	    ++curwin->w_cursor.lnum;
	    if (i == 0)
		curwin->w_cursor.col += bd.startspaces;
	}

	changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines);

	// Set '[ mark.
	curbuf->b_op_start = curwin->w_cursor;
	curbuf->b_op_start.lnum = lnum;

	// adjust '] mark
	curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1;
	curbuf->b_op_end.col = bd.textcol + totlen - 1;
	curbuf->b_op_end.coladd = 0;
	if (flags & PUT_CURSEND)
	{
	    colnr_T len;

	    curwin->w_cursor = curbuf->b_op_end;
	    curwin->w_cursor.col++;

	    // in Insert mode we might be after the NUL, correct for that
	    len = (colnr_T)STRLEN(ml_get_curline());
	    if (curwin->w_cursor.col > len)
		curwin->w_cursor.col = len;
	}
	else
	    curwin->w_cursor.lnum = lnum;
    }
    else
    {
	// Character or Line mode
	if (y_type == MCHAR)
	{
	    // if type is MCHAR, FORWARD is the same as BACKWARD on the next
	    // char
	    if (dir == FORWARD && gchar_cursor() != NUL)
	    {
		if (has_mbyte)
		{
		    int bytelen = (*mb_ptr2len)(ml_get_cursor());

		    // put it on the next of the multi-byte character.
		    col += bytelen;
		    if (yanklen)
		    {
			curwin->w_cursor.col += bytelen;
			curbuf->b_op_end.col += bytelen;
		    }
		}
		else
		{
		    ++col;
		    if (yanklen)
		    {
			++curwin->w_cursor.col;
			++curbuf->b_op_end.col;
		    }
		}
	    }
	    curbuf->b_op_start = curwin->w_cursor;
	}
	// Line mode: BACKWARD is the same as FORWARD on the previous line
	else if (dir == BACKWARD)
	    --lnum;
	new_cursor = curwin->w_cursor;

	// simple case: insert into one line at a time
	if (y_type == MCHAR && y_size == 1)
	{
	    linenr_T	end_lnum = 0; // init for gcc
	    linenr_T	start_lnum = lnum;
	    int		first_byte_off = 0;

	    if (VIsual_active)
	    {
		end_lnum = curbuf->b_visual.vi_end.lnum;
		if (end_lnum < curbuf->b_visual.vi_start.lnum)
		    end_lnum = curbuf->b_visual.vi_start.lnum;
		if (end_lnum > start_lnum)
		{
		    pos_T   pos;

		    // "col" is valid for the first line, in following lines
		    // the virtual column needs to be used.  Matters for
		    // multi-byte characters.
		    pos.lnum = lnum;
		    pos.col = col;
		    pos.coladd = 0;
		    getvcol(curwin, &pos, NULL, &vcol, NULL);
		}
	    }

	    if (count == 0 || yanklen == 0)
	    {
		if (VIsual_active)
		    lnum = end_lnum;
	    }
	    else if (count > INT_MAX / yanklen)
		// multiplication overflow
		emsg(_(e_resulting_text_too_long));
	    else
	    {
		totlen = count * yanklen;
		do {
		    oldp = ml_get(lnum);
		    oldlen = (int)STRLEN(oldp);
		    if (lnum > start_lnum)
		    {
			pos_T   pos;

			pos.lnum = lnum;
			if (getvpos(&pos, vcol) == OK)
			    col = pos.col;
			else
			    col = MAXCOL;
		    }
		    if (VIsual_active && col > oldlen)
		    {
			lnum++;
			continue;
		    }
		    newp = alloc(totlen + oldlen + 1);
		    if (newp == NULL)
			goto end;	// alloc() gave an error message
		    mch_memmove(newp, oldp, (size_t)col);
		    ptr = newp + col;
		    for (i = 0; i < count; ++i)
		    {
			mch_memmove(ptr, y_array[0], (size_t)yanklen);
			ptr += yanklen;
		    }
		    STRMOVE(ptr, oldp + col);
		    ml_replace(lnum, newp, FALSE);

		    // compute the byte offset for the last character
		    first_byte_off = mb_head_off(newp, ptr - 1);

		    // Place cursor on last putted char.
		    if (lnum == curwin->w_cursor.lnum)
		    {
			// make sure curwin->w_virtcol is updated
			changed_cline_bef_curs();
			curwin->w_cursor.col += (colnr_T)(totlen - 1);
		    }
		    if (VIsual_active)
			lnum++;
		} while (VIsual_active && lnum <= end_lnum);

		if (VIsual_active) // reset lnum to the last visual line
		    lnum--;
	    }

	    // put '] at the first byte of the last character
	    curbuf->b_op_end = curwin->w_cursor;
	    curbuf->b_op_end.col -= first_byte_off;

	    // For "CTRL-O p" in Insert mode, put cursor after last char
	    if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
		++curwin->w_cursor.col;
	    else
		curwin->w_cursor.col -= first_byte_off;
	    changed_bytes(lnum, col);
	}
	else
	{
	    linenr_T	new_lnum = new_cursor.lnum;
	    size_t	len;

	    // Insert at least one line.  When y_type is MCHAR, break the first
	    // line in two.
	    for (cnt = 1; cnt <= count; ++cnt)
	    {
		i = 0;
		if (y_type == MCHAR)
		{
		    // Split the current line in two at the insert position.
		    // First insert y_array[size - 1] in front of second line.
		    // Then append y_array[0] to first line.
		    lnum = new_cursor.lnum;
		    ptr = ml_get(lnum) + col;
		    totlen = (int)STRLEN(y_array[y_size - 1]);
		    newp = alloc(STRLEN(ptr) + totlen + 1);
		    if (newp == NULL)
			goto error;
		    STRCPY(newp, y_array[y_size - 1]);
		    STRCAT(newp, ptr);
		    // insert second line
		    ml_append(lnum, newp, (colnr_T)0, FALSE);
		    ++new_lnum;
		    vim_free(newp);

		    oldp = ml_get(lnum);
		    newp = alloc(col + yanklen + 1);
		    if (newp == NULL)
			goto error;
					    // copy first part of line
		    mch_memmove(newp, oldp, (size_t)col);
					    // append to first line
		    mch_memmove(newp + col, y_array[0], (size_t)(yanklen + 1));
		    ml_replace(lnum, newp, FALSE);

		    curwin->w_cursor.lnum = lnum;
		    i = 1;
		}

		for (; i < y_size; ++i)
		{
		    if (y_type != MCHAR || i < y_size - 1)
		    {
			if (ml_append(lnum, y_array[i], (colnr_T)0, FALSE)
								      == FAIL)
			    goto error;
			new_lnum++;
		    }
		    lnum++;
		    ++nr_lines;
		    if (flags & PUT_FIXINDENT)
		    {
			old_pos = curwin->w_cursor;
			curwin->w_cursor.lnum = lnum;
			ptr = ml_get(lnum);
			if (cnt == count && i == y_size - 1)
			    lendiff = (int)STRLEN(ptr);
			if (*ptr == '#' && preprocs_left())
			    indent = 0;     // Leave # lines at start
			else
			     if (*ptr == NUL)
			    indent = 0;     // Ignore empty lines
			else if (first_indent)
			{
			    indent_diff = orig_indent - get_indent();
			    indent = orig_indent;
			    first_indent = FALSE;
			}
			else if ((indent = get_indent() + indent_diff) < 0)
			    indent = 0;
			(void)set_indent(indent, 0);
			curwin->w_cursor = old_pos;
			// remember how many chars were removed
			if (cnt == count && i == y_size - 1)
			    lendiff -= (int)STRLEN(ml_get(lnum));
		    }
		}
		if (cnt == 1)
		    new_lnum = lnum;
	    }

error:
	    // Adjust marks.
	    if (y_type == MLINE)
	    {
		curbuf->b_op_start.col = 0;
		if (dir == FORWARD)
		    curbuf->b_op_start.lnum++;
	    }
	    // Skip mark_adjust when adding lines after the last one, there
	    // can't be marks there. But still needed in diff mode.
	    if (curbuf->b_op_start.lnum + (y_type == MCHAR) - 1 + nr_lines
						 < curbuf->b_ml.ml_line_count
#ifdef FEAT_DIFF
						 || curwin->w_p_diff
#endif
						 )
		mark_adjust(curbuf->b_op_start.lnum + (y_type == MCHAR),
					     (linenr_T)MAXLNUM, nr_lines, 0L);

	    // note changed text for displaying and folding
	    if (y_type == MCHAR)
		changed_lines(curwin->w_cursor.lnum, col,
					 curwin->w_cursor.lnum + 1, nr_lines);
	    else
		changed_lines(curbuf->b_op_start.lnum, 0,
					   curbuf->b_op_start.lnum, nr_lines);
	    if (y_current_used != NULL && (y_current_used != y_current
					     || y_current->y_array != y_array))
	    {
		// Something invoked through changed_lines() has changed the
		// yank buffer, e.g. a GUI clipboard callback.
		emsg(_(e_yank_register_changed_while_using_it));
		goto end;
	    }

	    // Put the '] mark on the first byte of the last inserted character.
	    // Correct the length for change in indent.
	    curbuf->b_op_end.lnum = new_lnum;
	    len = STRLEN(y_array[y_size - 1]);
	    col = (colnr_T)len - lendiff;
	    if (col > 1)
	    {
		curbuf->b_op_end.col = col - 1;
		if (len > 0)
		    curbuf->b_op_end.col -= mb_head_off(y_array[y_size - 1],
						y_array[y_size - 1] + len - 1);
	    }
	    else
		curbuf->b_op_end.col = 0;

	    if (flags & PUT_CURSLINE)
	    {
		// ":put": put cursor on last inserted line
		curwin->w_cursor.lnum = lnum;
		beginline(BL_WHITE | BL_FIX);
	    }
	    else if (flags & PUT_CURSEND)
	    {
		// put cursor after inserted text
		if (y_type == MLINE)
		{
		    if (lnum >= curbuf->b_ml.ml_line_count)
			curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
		    else
			curwin->w_cursor.lnum = lnum + 1;
		    curwin->w_cursor.col = 0;
		}
		else
		{
		    curwin->w_cursor.lnum = new_lnum;
		    curwin->w_cursor.col = col;
		    curbuf->b_op_end = curwin->w_cursor;
		    if (col > 1)
			curbuf->b_op_end.col = col - 1;
		}
	    }
	    else if (y_type == MLINE)
	    {
		// put cursor on first non-blank in first inserted line
		curwin->w_cursor.col = 0;
		if (dir == FORWARD)
		    ++curwin->w_cursor.lnum;
		beginline(BL_WHITE | BL_FIX);
	    }
	    else	// put cursor on first inserted character
		curwin->w_cursor = new_cursor;
	}
    }

    msgmore(nr_lines);
    curwin->w_set_curswant = TRUE;

end:
    if (cmdmod.cmod_flags & CMOD_LOCKMARKS)
    {
	curbuf->b_op_start = orig_start;
	curbuf->b_op_end = orig_end;
    }
    if (allocated)
	vim_free(insert_string);
    if (regname == '=')
	vim_free(y_array);

    VIsual_active = FALSE;

    // If the cursor is past the end of the line put it at the end.
    adjust_cursor_eol();
}

Heap-based Buffer Overflow in GitHub repository vim/vim prior to 9.0.


yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
{
    static int	    recursive = FALSE;
    dict_T	    *v_event;
    list_T	    *list;
    int		    n;
    char_u	    buf[NUMBUFLEN + 2];
    long	    reglen = 0;
    save_v_event_T  save_v_event;

    if (recursive)
	return;

    v_event = get_v_event(&save_v_event);

    list = list_alloc();
    if (list == NULL)
	return;

    // yanked text contents
    for (n = 0; n < reg->y_size; n++)
	list_append_string(list, reg->y_array[n], -1);
    list->lv_lock = VAR_FIXED;
    (void)dict_add_list(v_event, "regcontents", list);

    // register name or empty string for unnamed operation
    buf[0] = (char_u)oap->regname;
    buf[1] = NUL;
    (void)dict_add_string(v_event, "regname", buf);

    // motion type: inclusive or exclusive
    (void)dict_add_bool(v_event, "inclusive", oap->inclusive);

    // kind of operation (yank, delete, change)
    buf[0] = get_op_char(oap->op_type);
    buf[1] = get_extra_op_char(oap->op_type);
    buf[2] = NUL;
    (void)dict_add_string(v_event, "operator", buf);

    // register type
    buf[0] = NUL;
    buf[1] = NUL;
    switch (get_reg_type(oap->regname, &reglen))
    {
	case MLINE: buf[0] = 'V'; break;
	case MCHAR: buf[0] = 'v'; break;
	case MBLOCK:
		vim_snprintf((char *)buf, sizeof(buf), "%c%ld", Ctrl_V,
			     reglen + 1);
		break;
    }
    (void)dict_add_string(v_event, "regtype", buf);

    // selection type - visual or not
    (void)dict_add_bool(v_event, "visual", oap->is_VIsual);

    // Lock the dictionary and its keys
    dict_set_items_ro(v_event);

    recursive = TRUE;
    textlock++;
    apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, FALSE, curbuf);
    textlock--;
    recursive = FALSE;

    // Empty the dictionary, v:event is still valid
    restore_v_event(v_event, &save_v_event);
}


Fix oobread in java parser ##crash

* Reported by @bet4it via @huntrdev
* BountyID c8f4c2de-7d96-4ad4-857a-c099effca2d6
* Reproducer: bootstrap.class

R_API RBinJavaBootStrapArgument *r_bin_java_bootstrap_method_argument_new(ut8 *buffer, ut64 sz, ut64 buf_offset) {
	ut64 offset = 0;
	RBinJavaBootStrapArgument *bsm_arg = (RBinJavaBootStrapArgument *) malloc (sizeof (RBinJavaBootStrapArgument));
	if (!bsm_arg) {
		// TODO eprintf failed to allocate bytes for bootstrap_method.
		return bsm_arg;
	}
	memset (bsm_arg, 0, sizeof (RBinJavaBootStrapArgument));
	bsm_arg->file_offset = buf_offset;
	bsm_arg->argument_info_idx = R_BIN_JAVA_USHORT (buffer, offset);
	offset += 2;
	bsm_arg->argument_info_cp_obj = r_bin_java_clone_cp_idx (R_BIN_JAVA_GLOBAL_BIN, bsm_arg->argument_info_idx);
	bsm_arg->size = offset;
	return bsm_arg;
}

Out-of-bounds Read in r_bin_java_bootstrap_methods_attr_new function in GitHub repository radareorg/radare2 prior to 5.7.0. The bug causes the program reads data past the end 2f the intented buffer. Typically, this can allow attackers to read sensitive information from other memory locations or cause a crash. More details see [CWE-125: Out-of-bounds read](https://cwe.mitre.org/data/definitions/125.html).


R_API void r_bin_java_print_annotation_array_summary(RBinJavaAnnotationsArray *annotation_array) {
	RListIter *iter = NULL, *iter_tmp = NULL;
	RBinJavaAnnotation *annotation;
	if (!annotation_array->annotations) {
		// TODO eprintf
		return;
	}
	eprintf ("   Annotation Array Information:\n");
	eprintf ("   Number of Annotation Array Elements: %d\n", annotation_array->num_annotations);
	r_list_foreach_safe (annotation_array->annotations, iter, iter_tmp, annotation) {
		r_bin_java_print_annotation_summary (annotation);
	}
}


R_API char *r_bin_java_print_integer_cp_stringify(RBinJavaCPTypeObj *obj) {
	ut32 size = 255, consumed = 0;
	char *value = malloc (size);
	if (value) {
		memset (value, 0, size);
		consumed = snprintf (value, size, "%d.0x%04"PFMT64x ".%s.0x%08x",
			obj->metas->ord, obj->file_offset + obj->loadaddr, ((RBinJavaCPTypeMetas *) obj->metas->type_info)->name,
			R_BIN_JAVA_UINT (obj->info.cp_integer.bytes.raw, 0));
		if (consumed >= size - 1) {
			free (value);
			size += size >> 1;
			value = malloc (size);
			if (value) {
				memset (value, 0, size);
				(void)snprintf (value, size, "%d.0x%04"PFMT64x ".%s.0x%08x",
					obj->metas->ord, obj->file_offset + obj->loadaddr, ((RBinJavaCPTypeMetas *) obj->metas->type_info)->name,
					R_BIN_JAVA_UINT (obj->info.cp_integer.bytes.raw, 0));
			}
		}
	}
	return value;
}


R_API bool sdb_iterate_build_list(void *user, const char *k, const char *v) {
	RList *bin_objs_list = (RList *) user;
	size_t value = (size_t) sdb_atoi (v);
	RBinJavaObj *bin_obj = NULL;
	IFDBG eprintf ("Found %s == %"PFMT64x " bin_objs db\n", k, (ut64) value);
	if (value != 0 && value != (size_t) -1) {
		bin_obj = (RBinJavaObj *) value;
		r_list_append (bin_objs_list, bin_obj);
	}
	return true;
}


R_API ut64 r_bin_java_rtv_annotations_attr_calc_size(RBinJavaAttrInfo *attr) {
	ut64 size = 0;
	if (!attr) {
		// TODO eprintf allocation fail
		return size;
	}
	size += (6 + r_bin_java_annotation_array_calc_size (&(attr->info.annotation_array)));
	return size;
}


R_API RList *retrieve_all_method_access_string_and_value(void) {
	return retrieve_all_access_string_and_value (METHOD_ACCESS_FLAGS);
}


R_API void r_bin_java_print_line_number_attr_summary(RBinJavaLineNumberAttribute *lnattr) {
	if (!lnattr) {
		eprintf ("Attempting to print an invalid RBinJavaLineNumberAttribute *.\n");
		return;
	}
	printf ("  Line Number Attribute Offset: 0x%08"PFMT64x "\n", lnattr->file_offset);
	printf ("  Line Number Attribute StartPC: %d\n", lnattr->start_pc);
	printf ("  Line Number Attribute LineNumber: %d\n", lnattr->line_number);
}


R_API ut32 r_bin_java_get_utf8_len_from_cp_item_list(RList *cp_list, ut64 idx) {
	/*
	Search through the Constant Pool list for the given CP Index.
	If the idx not found by directly going to the list index,
	the list will be walked and then the IDX will be checked.
	rvalue: new ut32 .
	*/
	ut32 value = -1;
	RListIter *iter;
	if (!cp_list) {
		return 0;
	}
	RBinJavaCPTypeObj *item = (RBinJavaCPTypeObj *) r_list_get_n (cp_list, idx);
	if (item && (item->tag == R_BIN_JAVA_CP_UTF8) && item->metas->ord == idx) {
		value = item->info.cp_utf8.length;
	}
	if (value == -1) {
		r_list_foreach (cp_list, iter, item) {
			if (item && (item->tag == R_BIN_JAVA_CP_UTF8) && item->metas->ord == idx) {
				value = item->info.cp_utf8.length;
				break;
			}
		}
	}
	return value;
}


R_API RBinJavaStackMapFrame *r_bin_java_stack_map_frame_new(ut8 *buffer, ut64 sz, RBinJavaStackMapFrame *p_frame, ut64 buf_offset) {
	if (sz < 8) {
		return NULL;
	}
	RBinJavaStackMapFrame *stack_frame = r_bin_java_default_stack_frame ();
	RBinJavaVerificationObj *se = NULL;
	ut64 offset = 0;
	if (!stack_frame) {
		return NULL;
	}
	stack_frame->tag = buffer[offset];
	offset += 1;
	stack_frame->metas->type_info = (void *) r_bin_java_determine_stack_frame_type (stack_frame->tag);
	stack_frame->type = ((RBinJavaStackMapFrameMetas *) stack_frame->metas->type_info)->type;
	stack_frame->file_offset = buf_offset;
	stack_frame->p_stack_frame = p_frame;
	switch (stack_frame->type) {
	case R_BIN_JAVA_STACK_FRAME_SAME:
		// Maybe?  1. Copy the previous frames locals and set the locals count.
		// copy_type_info_to_stack_frame_list_up_to_idx (p_frame->local_items, stack_frame->local_items, idx);
		if (p_frame) {
			stack_frame->number_of_locals = p_frame->number_of_locals;
		} else {
			IFINT eprintf ("><?><\n");
			IFDBG eprintf ("Unable to set previous stackframe with the number of locals (current info.code_attr.implicit_frame was probably not set :/)");
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: TODO Stack Frame Same Locals Condition is untested, so there may be issues.\n");
		break;
	case R_BIN_JAVA_STACK_FRAME_SAME_LOCALS_1:
		// 1. Read the stack type
		stack_frame->number_of_stack_items = 1;
		se = r_bin_java_read_from_buffer_verification_info_new (buffer + offset, sz - offset, buf_offset + offset);
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsed R_BIN_JAVA_STACK_FRAME_SAME_LOCALS_1.\n");
		if (se) {
			offset += se->size;
		} else {
			eprintf ("r_bin_java_stack_map_frame_new: Unable to parse the Stack Items for the stack frame.\n");
			r_bin_java_stack_frame_free (stack_frame);
			return NULL;
		}
		r_list_append (stack_frame->stack_items, (void *) se);
		// Maybe?  3. Copy the previous frames locals and set the locals count.
		// copy_type_info_to_stack_frame_list_up_to_idx (p_frame->local_items, stack_frame->local_items, idx);
		if (p_frame) {
			stack_frame->number_of_locals = p_frame->number_of_locals;
		} else {
			IFDBG eprintf ("Unable to set previous stackframe with the number of locals (current info.code_attr.implicit_frame was probably not set :/)");
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: TODO Stack Frame Same Locals 1 Stack Element Condition is untested, so there may be issues.\n");
		break;
	case R_BIN_JAVA_STACK_FRAME_CHOP:
		// 1. Calculate the max index we want to copy from the list of the
		// previous frames locals
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsing R_BIN_JAVA_STACK_FRAME_CHOP.\n");
		// ut16 k = 251 - stack_frame->tag;
		/*,
		idx = p_frame->number_of_locals - k;
		*/
		// 2.  read the uoffset value
		stack_frame->offset_delta = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// Maybe? 3. Copy the previous frames locals and set the locals count.
		// copy_type_info_to_stack_frame_list_up_to_idx (p_frame->local_items, stack_frame->local_items, idx);
		if (p_frame) {
			stack_frame->number_of_locals = p_frame->number_of_locals;
		} else {
			IFINT eprintf ("><?><\n");
			IFDBG eprintf ("Unable to set previous stackframe with the number of locals (current info.code_attr.implicit_frame was probably not set :/)");
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: TODO Stack Frame Chop Condition is untested, so there may be issues.\n");
		break;
	case R_BIN_JAVA_STACK_FRAME_SAME_FRAME_EXTENDED:
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsing R_BIN_JAVA_STACK_FRAME_SAME_FRAME_EXTENDED.\n");
		// 1. Read the uoffset
		stack_frame->offset_delta = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// 2. Read the stack element type
		stack_frame->number_of_stack_items = 1;
		se = r_bin_java_read_from_buffer_verification_info_new (buffer + offset, sz - offset, buf_offset + offset);
		if (se) {
			offset += se->size;
		} else {
			eprintf ("r_bin_java_stack_map_frame_new: Unable to parse the Stack Items for the stack frame.\n");
			r_bin_java_stack_frame_free (stack_frame);
			return NULL;
		}
		r_list_append (stack_frame->stack_items, (void *) se);
		// Maybe? 3. Copy the previous frames locals to the current locals
		// copy_type_info_to_stack_frame_list_up_to_idx (p_frame->local_items, stack_frame->local_items, idx);
		if (p_frame) {
			stack_frame->number_of_locals = p_frame->number_of_locals;
		} else {
			IFINT eprintf ("><?><\n");
			IFDBG eprintf ("Unable to set previous stackframe with the number of locals (current info.code_attr.implicit_frame was probably not set :/)");
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: TODO Stack Frame Same Locals Frame Stack 1 Extended Condition is untested, so there may be issues.\n");
		break;
	case R_BIN_JAVA_STACK_FRAME_APPEND:
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsing R_BIN_JAVA_STACK_FRAME_APPEND.\n");
		// 1. Calculate the max index we want to copy from the list of the
		// previous frames locals
		ut16 k = stack_frame->tag - 251;
		ut32 i = 0;
		// 2. Read the uoffset
		stack_frame->offset_delta = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// Maybe? 3. Copy the previous frames locals to the current locals
		// copy_type_info_to_stack_frame_list_up_to_idx (p_frame->local_items, stack_frame->local_items, idx);
		// 4. Read off the rest of the appended locals types
		for (i = 0; i < k; i++) {
			if (offset >= sz) {
				break;
			}
			IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsing verifying the k'th frame: %d of %d.\n", i, k);
			se = r_bin_java_read_from_buffer_verification_info_new (buffer + offset, sz - offset, buf_offset + offset);
			IFDBG eprintf ("r_bin_java_stack_map_frame_new: Completed Parsing\n");
			if (se) {
				offset += se->size;
			} else {
				eprintf ("r_bin_java_stack_map_frame_new: Unable to parse the locals for the stack frame.\n");
				r_bin_java_stack_frame_free (stack_frame);
				return NULL;
			}
			r_list_append (stack_frame->local_items, (void *) se);
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Breaking out of loop");
		IFDBG eprintf ("p_frame: %p\n", p_frame);
		if (p_frame) {
			stack_frame->number_of_locals = p_frame->number_of_locals + k;
		} else {
			IFINT eprintf ("><?><\n");
			IFDBG eprintf ("Unable to set previous stackframe with the number of locals (current info.code_attr.implicit_frame was probably not set :/)");
		}
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: TODO Stack Frame Same Locals Frame Stack 1 Extended Condition is untested, so there may be issues.\n");
		break;
	case R_BIN_JAVA_STACK_FRAME_FULL_FRAME:
		IFDBG eprintf ("r_bin_java_stack_map_frame_new: Parsing R_BIN_JAVA_STACK_FRAME_FULL_FRAME.\n");
		stack_frame->offset_delta = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// IFDBG eprintf ("r_bin_java_stack_map_frame_new: Code Size > 65535, read(%d byte(s)), offset = 0x%08x.\n", var_sz, stack_frame->offset_delta);
		// Read the number of variables based on the max # local variable
		stack_frame->number_of_locals = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// IFDBG eprintf ("r_bin_java_stack_map_frame_new: Max ulocalvar > 65535, read(%d byte(s)), number_of_locals = 0x%08x.\n", var_sz, stack_frame->number_of_locals);
		IFDBG r_bin_java_print_stack_map_frame_summary(stack_frame);
		// read the number of locals off the stack
		for (i = 0; i < stack_frame->number_of_locals; i++) {
			if (offset >= sz) {
				break;
			}
			se = r_bin_java_read_from_buffer_verification_info_new (buffer + offset, sz - offset, buf_offset + offset);
			if (se) {
				offset += se->size;
				// r_list_append (stack_frame->local_items, (void *) se);
			} else {
				eprintf ("r_bin_java_stack_map_frame_new: Unable to parse the locals for the stack frame.\n");
				r_bin_java_stack_frame_free (stack_frame);
				return NULL;
			}
			r_list_append (stack_frame->local_items, (void *) se);
		}
		// Read the number of stack items based on the max size of stack
		stack_frame->number_of_stack_items = R_BIN_JAVA_USHORT (buffer, offset);
		offset += 2;
		// IFDBG eprintf ("r_bin_java_stack_map_frame_new: Max ustack items > 65535, read(%d byte(s)), number_of_locals = 0x%08x.\n", var_sz, stack_frame->number_of_stack_items);
		// read the stack items
		for (i = 0; i < stack_frame->number_of_stack_items; i++) {
			if (offset >= sz) {
				break;
			}
			se = r_bin_java_read_from_buffer_verification_info_new (buffer + offset, sz - offset, buf_offset + offset);
			if (se) {
				offset += se->size;
				// r_list_append (stack_frame->stack_items, (void *) se);
			} else {
				eprintf ("r_bin_java_stack_map_frame_new: Unable to parse the stack items for the stack frame.\n");
				r_bin_java_stack_frame_free (stack_frame);
				return NULL;
			}
			r_list_append (stack_frame->local_items, (void *) se);
		}
		break;
	default:
		eprintf ("java: Unknown type\n");
		break;
	}
	// IFDBG eprintf ("Created a stack frame at offset(0x%08"PFMT64x") of size: %d\n", buf_offset, stack_frame->size);//r_bin_java_print_stack_map_frame_summary(stack_frame);
	stack_frame->size = offset;
	// IFDBG r_bin_java_print_stack_map_frame_summary(stack_frame);
	return stack_frame;
}


R_API char *get_type_value_str(const char *arg_str, ut8 array_cnt) {
	ut32 str_len = array_cnt ? (array_cnt + 1) * 2 + strlen (arg_str) : strlen (arg_str);
	char *str = malloc (str_len + 1);
	ut32 bytes_written = snprintf (str, str_len + 1, "%s", arg_str);
	while (array_cnt > 0) {
		strcpy (str + bytes_written, "[]");
		bytes_written += 2;
		array_cnt--;
	}
	return str;
}


R_API void r_bin_java_print_source_code_file_attr_summary(RBinJavaAttrInfo *attr) {
	if (!attr) {
		eprintf ("Attempting to print an invalid RBinJavaAttrInfo *SourceFile.\n");
		return;
	}
	printf ("Source File Attribute Information:\n");
	printf ("  Attribute Offset: 0x%08"PFMT64x "\n", attr->file_offset);
	printf ("  Attribute Name Index: %d (%s)\n", attr->name_idx, attr->name);
	printf ("  Attribute Length: %d\n", attr->length);
	printf ("  Source File Index: %d\n", attr->info.source_file_attr.sourcefile_idx);
}


R_API void *r_bin_java_free(RBinJavaObj *bin) {
	char *bin_obj_key = NULL;
	if (!bin) {
		return NULL;
	}
	// Delete the bin object from the data base.
	bin_obj_key = r_bin_java_build_obj_key (bin);
	// if (bin->AllJavaBinObjs && sdb_exists (bin->AllJavaBinObjs, bin_obj_key)) {
	// sdb_unset (bin->AllJavaBinObjs, bin_obj_key, 0);
	// }
	free (bin_obj_key);
	r_list_free (bin->imports_list);
	// XXX - Need to remove all keys belonging to this class from
	// the share meta information sdb.
	// TODO e.g. iterate over bin->kv and delete all obj, func, etc. keys
	// sdb_free (bin->kv);
	// free up the constant pool list
	r_list_free (bin->cp_list);
	// free up the fields list
	r_list_free (bin->fields_list);
	// free up methods list
	r_list_free (bin->methods_list);
	// free up interfaces list
	r_list_free (bin->interfaces_list);
	r_list_free (bin->attrs_list);
	// TODO: XXX if a class list of all inner classes
	// are formed then this will need to be updated
	free (bin->cf2.flags_str);
	free (bin->cf2.this_class_name);
	if (bin == R_BIN_JAVA_GLOBAL_BIN) {
		R_BIN_JAVA_GLOBAL_BIN = NULL;
	}
	free (bin->file);
	r_str_constpool_fini (&bin->constpool);
	free (bin);
	return NULL;
}


R_API char *r_bin_java_unmangle_method(const char *flags, const char *name, const char *params, const char *r_value) {
	RList *the_list = params ? r_bin_java_extract_type_values (params) : r_list_new ();
	RListIter *iter = NULL;
	// second case removes leading space if no flags are given
	const char *fmt = flags ? "%s %s %s (%s)" : "%s%s %s (%s)";
	char *str = NULL, *f_val_str = NULL, *r_val_str = NULL, *prototype = NULL, *p_val_str = NULL;
	ut32 params_idx = 0, params_len = 0, prototype_len = 0;
	if (!extract_type_value (r_value, &r_val_str)) {
		r_list_free (the_list);
		return NULL;
	}
	if (!r_val_str) {
		r_val_str = strdup ("UNKNOWN");
	}
	f_val_str = strdup (r_str_get (flags));
	r_list_foreach (the_list, iter, str) {
		params_len += strlen (str);
		if (params_idx > 0) {
			params_len += 2;
		}
		params_idx++;
	}
	if (params_len > 0) {
		ut32 offset = 0;
		params_len += 1;
		p_val_str = malloc (params_len);
		r_list_foreach (the_list, iter, str) {
			if (offset != 0) {
				offset += snprintf (p_val_str + offset, params_len - offset, ", %s", str);
			} else {
				offset += snprintf (p_val_str + offset, params_len - offset, "%s", str);
			}
		}
	} else {
		p_val_str = strdup ("");
	}

	prototype_len += (flags ? strlen (flags) + 1 : 0); // space vs no space
	prototype_len += strlen (name) + 1; // name + space
	prototype_len += strlen (r_val_str) + 1; // r_value + space
	prototype_len += strlen (p_val_str) + 3; // space + l_paren + params + r_paren
	prototype_len += 1; // null
	prototype = malloc (prototype_len);
	/// TODO enable this function and start using it to demangle strings
	snprintf (prototype, prototype_len, fmt, f_val_str, r_val_str, name, p_val_str);
	free (f_val_str);
	free (r_val_str);
	free (p_val_str);
	r_list_free (the_list);
	return prototype;
}