#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static unsigned int
parse_number(const char *text)
{
	char *end;
	unsigned long value;

	errno = 0;
	value = strtoul(text, &end, 0);
	if (errno != 0 || *end != '\0' || value > UINT_MAX)
		errx(2, "invalid number: %s", text);
	return ((unsigned int)value);
}

int
main(int argc, char **argv)
{
	struct stat sb;
	unsigned int expected_major, expected_minor;
	bool is_device;

	if (argc != 3 && argc != 5)
		errx(2, "usage: stat_special char|block path major minor | "
		    "stat_special fifo path");
	if (lstat(argv[2], &sb) != 0)
		err(1, "lstat %s", argv[2]);
	is_device = strcmp(argv[1], "char") == 0 || strcmp(argv[1], "block") == 0;
	if (is_device) {
		if (argc != 5)
			errx(2, "device checks require major and minor");
		expected_major = parse_number(argv[3]);
		expected_minor = parse_number(argv[4]);
		if ((strcmp(argv[1], "char") == 0 && !S_ISCHR(sb.st_mode)) ||
		    (strcmp(argv[1], "block") == 0 && !S_ISBLK(sb.st_mode)))
			errx(1, "%s has the wrong file type", argv[2]);
		if ((unsigned int)major(sb.st_rdev) != expected_major ||
		    (unsigned int)minor(sb.st_rdev) != expected_minor)
			errx(1, "%s rdev=%#jx major=%u minor=%u, expected %u:%u",
			    argv[2], (uintmax_t)sb.st_rdev,
			    (unsigned int)major(sb.st_rdev),
			    (unsigned int)minor(sb.st_rdev), expected_major,
			    expected_minor);
	} else if (strcmp(argv[1], "fifo") == 0) {
		if (argc != 3)
			errx(2, "FIFO checks do not take major and minor");
		if (!S_ISFIFO(sb.st_mode))
			errx(1, "%s is not a FIFO", argv[2]);
		if (sb.st_rdev != NODEV)
			errx(1, "%s FIFO st_rdev=%#jx, expected NODEV", argv[2],
			    (uintmax_t)sb.st_rdev);
	} else {
		errx(2, "unknown type: %s", argv[1]);
	}
	printf("PASS path=%s type=%s rdev=%#jx major=%u minor=%u\n", argv[2],
	    argv[1], (uintmax_t)sb.st_rdev, (unsigned int)major(sb.st_rdev),
	    (unsigned int)minor(sb.st_rdev));
	return (0);
}
