/*
 * kdfontop.c - export getfont(), getfontsize() and putfont()
 *
 * Font handling differs between various kernel versions.
 * Hide the differences in this file.
 */
#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <stdlib.h> /* free() */
#include <sys/ioctl.h>
#include <linux/kd.h>
#include "kdfontop.h"
#include "nls.h"
#include "version.h"

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

int restorefont(int fd)
{
	if (ioctl(fd, PIO_FONTRESET, 0)) {
		perror("PIO_FONTRESET");
		return -1;
	}
	return 0;
}

int font_charheight(unsigned char *buf, int count, int width)
{
	int h, i, x;
	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;
}

/*
 * 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 getfont(int fd, unsigned char *buf, int *count, int *width, int *height)
{
	struct consolefontdesc cfd;
	struct console_font_op cfo;
	int i;

	/* First attempt: KDFONTOP */
	cfo.op    = KD_FONT_OP_GET;
	cfo.flags = 0;
	cfo.width = cfo.height = 32;
	cfo.charcount          = *count;
	cfo.data               = buf;
	i                      = ioctl(fd, KDFONTOP, &cfo);
	if (i == 0) {
		*count = cfo.charcount;
		if (height)
			*height = cfo.height;
		if (width)
			*width = cfo.width;
		return 0;
	}
	if (errno != ENOSYS && errno != EINVAL) {
		perror("getfont: KDFONTOP");
		return -1;
	}

	/* The other methods do not support width != 8 */
	if (width)
		*width = 8;
	/* Second attempt: GIO_FONTX */
	cfd.charcount  = *count;
	cfd.charheight = 0;
	cfd.chardata   = (char *)buf;
	i              = ioctl(fd, GIO_FONTX, &cfd);
	if (i == 0) {
		*count = cfd.charcount;
		if (height)
			*height = cfd.charheight;
		return 0;
	}
	if (errno != ENOSYS && errno != EINVAL) {
		perror("getfont: GIO_FONTX");
		return -1;
	}

	/* Third attempt: GIO_FONT */
	if (*count < 256) {
		fprintf(stderr, _("bug: getfont called with count<256\n"));
		return -1;
	}
	if (!buf) {
		fprintf(stderr, _("bug: getfont using GIO_FONT needs buf.\n"));
		return -1;
	}
	i = ioctl(fd, GIO_FONT, buf);
	if (i) {
		perror("getfont: GIO_FONT");
		return -1;
	}
	*count = 256;
	if (height)
		*height = 0; /* undefined, at most 32 */
	return 0;
}

int getfontsize(int fd)
{
	int count;
	int i;

	count = 0;
	i     = getfont(fd, NULL, &count, NULL, NULL);
	return (i == 0) ? count : 256;
}

int putfont(int fd, unsigned char *buf, int count, int width, int height)
{
	struct consolefontdesc cfd;
	struct console_font_op cfo;
	int i;

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

	/* First attempt: KDFONTOP */
	cfo.op        = KD_FONT_OP_SET;
	cfo.flags     = 0;
	cfo.width     = width;
	cfo.height    = height;
	cfo.charcount = count;
	cfo.data      = buf;
	i             = ioctl(fd, KDFONTOP, &cfo);
	if (i == 0)
		return 0;
	if (width != 8 || (errno != ENOSYS && errno != EINVAL)) {
		perror("putfont: KDFONTOP");
		return -1;
	}

	/* Variation on first attempt: in case count is not 256 or 512
	   round up and try again. */
	if (errno == EINVAL && width == 8 && count != 256 && count < 512) {
		int ct               = ((count > 256) ? 512 : 256);
		unsigned char *mybuf = malloc(32 * ct);

		if (!mybuf) {
			fprintf(stderr, _("%s: out of memory\n"), progname);
			return -1;
		}
		memset(mybuf, 0, 32 * ct);
		memcpy(mybuf, buf, 32 * count);
		cfo.data      = mybuf;
		cfo.charcount = ct;
		i             = ioctl(fd, KDFONTOP, &cfo);
		free(mybuf);
		if (i == 0)
			return 0;
	}

	/* Second attempt: PIO_FONTX */
	cfd.charcount  = count;
	cfd.charheight = height;
	cfd.chardata   = (char *)buf;
	i              = ioctl(fd, PIO_FONTX, &cfd);
	if (i == 0)
		return 0;
	if (errno != ENOSYS && errno != EINVAL) {
		fprintf(stderr, "%s: putfont: %d,%dx%d:failed: %d\n", progname, count, width, height, i);
		perror("putfont: PIO_FONTX");
		return -1;
	}

	/* Third attempt: PIO_FONT */
	/* This will load precisely 256 chars, independent of count */
	i = ioctl(fd, PIO_FONT, buf);
	if (i) {
		fprintf(stderr, "%s: putfont: %d,%dx%d:  failed: %d\n", progname, count, width, height, i);
		perror("putfont: PIO_FONT");
		return -1;
	}
	return 0;
}
