/*
 * DF-3159: ftruncate() a regular file on a 64-KiB-cluster FAT32 volume
 * past the 32-bit MSDOSFS_FILESIZE_MAX (0xFFFFFFFF) that msdosfs_write
 * enforces (msdosfs_vnops.c:618) but msdosfs_setattr -> detrunc ->
 * deextend does not (msdosfs_denode.c:509-511, 618-647).
 *
 * de_FileSize becomes 0x100010000 (4 GiB + 64 KiB); DE_EXTERNALIZE
 * putulong()s the dirent size field to the low 32 bits (0x10000).  After
 * unmount+remount the file has silently shrunk.
 *
 * usage: trunc <file> [size-bytes-hex]
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char **argv)
{
	const char *path;
	unsigned long long sz = 0x100010000ULL;
	struct stat st;
	int fd;

	if (argc < 2) {
		fprintf(stderr, "usage: trunc <file> [size]\n");
		return 2;
	}
	path = argv[1];
	if (argc >= 3)
		sz = strtoull(argv[2], NULL, 0);

	fd = open(path, O_RDWR);
	if (fd < 0) { perror("open"); return 2; }
	if (fstat(fd, &st)) { perror("fstat"); return 2; }
	printf("before: size %jd\n", (intmax_t)st.st_size);

	if (ftruncate(fd, (off_t)sz) < 0) {
		printf("ftruncate(0x%llx) FAILED: errno=%d (%s)  <-- EFBIG expected, got this\n",
		       sz, errno, strerror(errno));
		close(fd);
		return 1;
	}
	if (fstat(fd, &st)) { perror("fstat2"); return 2; }
	printf("ftruncate(0x%llx) OK; in-memory size now %jd (0x%jx)\n",
	       sz, (intmax_t)st.st_size, (uintmax_t)st.st_size);
	if (st.st_size > 0xFFFFFFFFULL)
		printf("NOTE: size exceeds MSDOSFS_FILESIZE_MAX (0xFFFFFFFF); "
		       "dirent field is 32-bit\n");
	close(fd);
	return 0;
}
