// SPDX-License-Identifier: LGPL-2.0-or-later
/*
 * Copyright (C) 2007-2020 Alexey Gladkov <gladkov.alexey@gmail.com>
 *
 * Originally written by Andries Brouwer
 */
#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <stdlib.h> /* free() */
#include <string.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <linux/kd.h>

#include "kfontP.h"

#ifdef COMPAT_HEADERS
#include "compat/linux-kd.h"
#endif

static int
is_kd_text_mode(struct kfont_context *ctx, int fd, int report_errors)
{
	unsigned int kd_mode;

	if (kfont_ioctl(ctx, fd, KDGETMODE, &kd_mode)) {
		if (report_errors)
			KFONT_ERR(ctx, "ioctl(KDGETMODE): %m");
		return 0;
	}

	if (kd_mode == KD_TEXT)
		return 1;

	if (report_errors)
		KFONT_ERR(ctx, _("Console is not in text mode"));
	return 0;
}

static inline int
is_kd_text(struct kfont_context *ctx, int fd)
{
	return is_kd_text_mode(ctx, fd, 1);
}

int
kfont_restore_font(struct kfont_context *ctx, int fd)
{
	struct console_font_op cfo;

	if (!is_kd_text(ctx, fd))
		return -1;

	cfo.op = KD_FONT_OP_SET_DEFAULT;
	cfo.data = NULL;

	if (kfont_ioctl(ctx, fd, KDFONTOP, &cfo)) {
		KFONT_ERR(ctx, "ioctl(KD_FONT_OP_SET_DEFAULT): %m");
		return -1;
	}
	return 0;
}

unsigned int
font_charheight(unsigned char *buf, unsigned int count, unsigned int width)
{
	unsigned int h, i, x;
	unsigned int bytewidth = (width + 7) / 8;

	for (h = 32; h > 0; h--)
		for (i = 0; i < count; i++)
			for (x = 0; x < bytewidth; x++)
				if (buf[(32 * i + h - 1) * bytewidth + x])
					goto nonzero;
nonzero:
	return h;
}

static int
get_font_kdfontop(struct kfont_context *ctx, int consolefd,
		unsigned char *buf,
		unsigned int *count,
		unsigned int *width,
		unsigned int *height,
		unsigned int *vpitch)
{
	struct console_font_op cfo;

	if (!is_kd_text(ctx, consolefd))
		return -1;

	cfo.op = KD_FONT_OP_GET;
	cfo.flags = 0;
	cfo.width = 32;
	cfo.height = 32;
	cfo.charcount = (sizeof(unsigned char) * MAXFONTSIZE) / (64 * 128 / 8); /* max size 64x128, 8 bits/byte */;
	cfo.data = NULL;

	/*
	 * Check font height and width. We can't do this in one request because
	 * if KD_FONT_OP_GET_TALL is used then vpitch will be less than 32 for
	 * 8x16 fonts which will break saving the font to a file. After that,
	 * such a saved font cannot be distinguished from the old fonts by the
	 * header.
	 *
	 * When we learn how to take into account vpitch in psf format, then
	 * this code can be redone.
	 */
	while (1) {
		errno = 0;
		if (kfont_ioctl(ctx, consolefd, KDFONTOP, &cfo)) {
#ifdef KD_FONT_OP_GET_TALL
			if (errno == ENOSPC && cfo.op != KD_FONT_OP_GET_TALL) {
				/*
				 * It looks like the font is larger than the
				 * regular font and we need to check for tall
				 * font.
				 */
				cfo.op = KD_FONT_OP_GET_TALL;
				cfo.width = 64;
				cfo.height = 128;
				continue;
			}
#endif
			KFONT_ERR(ctx, "ioctl(KDFONTOP): %m");
			return -1;
		}
		break;
	}

	if (buf) {
		/* actually get font height and width */
		cfo.data = buf;
		cfo.charcount = *count;

		errno = 0;
		if (kfont_ioctl(ctx, consolefd, KDFONTOP, &cfo)) {
			if (errno != ENOSYS && errno != EINVAL) {
				KFONT_ERR(ctx, "ioctl(KDFONTOP): %m");
			}
			return -1;
		}
	}

	*count = cfo.charcount;
	if (height)
		*height = cfo.height;
	if (width)
		*width = cfo.width;
	if (vpitch) {
#ifdef KD_FONT_OP_GET_TALL
		if (cfo.op == KD_FONT_OP_GET_TALL)
			*vpitch = cfo.height;
		else
#endif
			*vpitch = 32;
	}
	return 0;
}

/*
 * May be called with buf==NULL if we only want info.
 * May be called with width==NULL and height==NULL.
 * Must not exit - we may have cleanup to do.
 */
int
kfont_get_font(struct kfont_context *ctx, int fd, unsigned char *buf,
		unsigned int *count,
		unsigned int *width,
		unsigned int *height,
		unsigned int *vpitch)
{
	return get_font_kdfontop(ctx, fd, buf, count, width, height, vpitch);
}

int unsigned
kfont_get_fontsize(struct kfont_context *ctx, int fd)
{
	unsigned int count = 0;
	if (!kfont_get_font(ctx, fd, NULL, &count, NULL, NULL, NULL))
		return count;
	return 256;
}

int
kfont_is_font_console(struct kfont_context *ctx, int fd)
{
	if (!is_kd_text_mode(ctx, fd, 0))
		return 0;

	struct console_font_op cfo = {
		.op        = KD_FONT_OP_GET,
		.flags     = 0,
		.width     = 32,
		.height    = 32,
		.charcount = (sizeof(unsigned char) * MAXFONTSIZE) / (64 * 128 / 8),
		.data      = NULL,
	};

	errno = 0;
	kfont_ioctl(ctx, fd, KDFONTOP, &cfo);

	return (errno != ENOSYS && errno != ENOTTY);
}

static int
put_font_kdfontop(struct kfont_context *ctx, int consolefd, unsigned char *buf,
		unsigned int count,
		unsigned int width,
		unsigned int height,
		unsigned int vpitch)
{
	struct console_font_op cfo;

	if (!is_kd_text(ctx, consolefd))
		return -1;

	if (vpitch == 32 && width <= 32)
		cfo.op        = KD_FONT_OP_SET;
	else {
#ifdef KD_FONT_OP_SET_TALL
		cfo.op        = KD_FONT_OP_SET_TALL;
#else
		KFONT_ERR(ctx, _("tall font not supported"));
		return -1;
#endif
	}
	cfo.flags     = 0;
	cfo.width     = width;
	cfo.height    = height;
	cfo.charcount = count;
	cfo.data      = buf;

	errno = 0;

	if (!kfont_ioctl(ctx, consolefd, KDFONTOP, &cfo))
		return 0;

	if (errno == ENOSYS) {
		KFONT_ERR(ctx, _("Unable to load such font with such kernel version"));
		return -1;
	}

	int ret = -1;

	/* In case count is not 256 or 512 round up and try again. */
	if (errno == EINVAL && width == 8 && count != 256 && count < 512) {
		unsigned int ct = ((count > 256) ? 512 : 256);
		unsigned char *mybuf = calloc(ct, 32U);

		if (!mybuf) {
			KFONT_ERR(ctx, "calloc: %m");
			return -1;
		}

		memcpy(mybuf, buf, 32U * count);

		cfo.data      = mybuf;
		cfo.charcount = ct;

		errno = 0;

		ret = kfont_ioctl(ctx, consolefd, KDFONTOP, &cfo);
		free(mybuf);
	}

	if (ret)
		KFONT_ERR(ctx, "ioctl(KDFONTOP): %m");
	return ret;
}

int
kfont_put_font(struct kfont_context *ctx, int fd, unsigned char *buf, unsigned int count,
        unsigned int width, unsigned int height, unsigned int vpitch)
{
	if (!width)
		width = 8;

	if (!height)
		height = font_charheight(buf, count, width);

	return put_font_kdfontop(ctx, fd, buf, count, width, height, vpitch);
}
