#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>

static char probe_path[MAXPATHLEN];
static int probe_result = -1;

static int
probe_run(SYSCTL_HANDLER_ARGS)
{
	struct nameidata nd;
	struct vattr attributes;
	struct vnode *vnode;
	int error, operation;

	operation = 0;
	error = sysctl_handle_int(oidp, &operation, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (operation < 1 || operation > 5 || probe_path[0] == '\0')
		return (EINVAL);

	NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, probe_path);
	error = namei(&nd);
	if (error != 0) {
		probe_result = error;
		return (0);
	}
	vnode = nd.ni_vp;
	NDFREE_PNBUF(&nd);
	VATTR_NULL(&attributes);
	attributes.va_size = 0;
	if (operation == 2 || operation == 5)
		attributes.va_mode = 0600;
	if (operation == 3 || operation == 5) {
		attributes.va_uid = 1;
		attributes.va_gid = 1;
	}
	if (operation == 4 || operation == 5) {
		attributes.va_atime.tv_sec = 1;
		attributes.va_atime.tv_nsec = 0;
		attributes.va_mtime.tv_sec = 1;
		attributes.va_mtime.tv_nsec = 0;
	}
	probe_result = VOP_SETATTR(vnode, &attributes, curthread->td_ucred);
	vput(vnode);
	return (0);
}

static int
probe_modevent(module_t module, int event, void *arg)
{
	(void)module;
	(void)arg;
	switch (event) {
	case MOD_LOAD:
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

SYSCTL_NODE(_debug, OID_AUTO, erofs_setattr_probe,
    CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "EROFS setattr regression probe");
SYSCTL_STRING(_debug_erofs_setattr_probe, OID_AUTO, path, CTLFLAG_RW,
    probe_path, sizeof(probe_path), "Target vnode path");
SYSCTL_PROC(_debug_erofs_setattr_probe, OID_AUTO, run,
    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, 0, probe_run, "I",
    "1=size; 2=size+mode; 3=size+owner; 4=size+times; 5=all");
SYSCTL_INT(_debug_erofs_setattr_probe, OID_AUTO, result, CTLFLAG_RD,
    &probe_result, 0, "Last VOP_SETATTR errno");

static moduledata_t probe_module = {
	"erofs_setattr_probe",
	probe_modevent,
	NULL,
};

DECLARE_MODULE(erofs_setattr_probe, probe_module, SI_SUB_DRIVERS,
    SI_ORDER_MIDDLE);
MODULE_VERSION(erofs_setattr_probe, 1);
