SchOS/cmd/schmode/main.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

109 lines
3.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// schmode -- change the display mode.
//
// docs/plan/hybrid.md milestone H3. The interesting thing about this program
// is how little it is: it opens a socket to `vgpud` and writes a line. No
// syscall it makes has heard of a GPU, no kernel code runs that knows what a
// scanout is, and if the driver on the other end gets the sequence wrong the
// worst that happens is that one process dies and schinit starts another.
//
// That is the whole argument for moving the driver out, stated as a program
// rather than as a paragraph. An in-kernel driver could do the same modeset,
// and the same mistake would take the machine.
//
// schmode what the display is doing now
// schmode modes what it will accept
// schmode 1024 768 set it
// schmode 1024x768 the same
#include "types.h"
#include "user.h"
#include "socket.h"
#define VGPUD_SOCKET "/run/vgpud"
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
static int
dial(void)
{
struct sockaddr_un sa;
int fd, i;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return -1;
for (i = 0; i < (int)sizeof(sa.sun_path); i++)
sa.sun_path[i] = 0;
sa.sun_family = AF_UNIX;
for (i = 0; VGPUD_SOCKET[i] && i < (int)sizeof(sa.sun_path) - 1; i++)
sa.sun_path[i] = VGPUD_SOCKET[i];
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
close(fd);
return -1;
}
return fd;
}
int
main(int argc, char *argv[])
{
char buf[256];
char line[128];
int fd, n = 0;
fd = dial();
if (fd < 0) {
// Naming the likely cause rather than the errno. On a machine with no
// virtio-gpu, vgpud reports that and exits on purpose -- see
// SCHINIT_EXIT_NORESTART -- so "not running" is the ordinary case and
// not a fault.
puts_("schmode: cannot reach vgpud on " VGPUD_SOCKET "\n");
puts_(" it is not running -- check: cat /proc/schinit\n");
return 1;
}
// Everything after the program name, rejoined. "1024 768" and "1024x768"
// both arrive as one line either way, because the server parses digits and
// skips whatever separates them.
if (argc < 2) {
line[n++] = 'i';
} else {
if (argv[1][0] >= '0' && argv[1][0] <= '9') {
// A bare size is a set, not a subcommand.
line[n++] = 'm'; line[n++] = 'o'; line[n++] = 'd';
line[n++] = 'e'; line[n++] = ' ';
}
for (int i = 1; i < argc; i++) {
if (i > 1 && n < (int)sizeof(line) - 1)
line[n++] = ' ';
for (int j = 0; argv[i][j] && n < (int)sizeof(line) - 1; j++)
line[n++] = argv[i][j];
}
}
line[n++] = '\n';
if (write(fd, line, n) != n) {
puts_("schmode: vgpud would not take that\n");
close(fd);
return 1;
}
// One answer, then leave. The server writes its reply and waits for the
// next line; it does not close, so reading to EOF would wait for ever.
n = read(fd, buf, sizeof(buf));
if (n > 0)
write(1, buf, n);
close(fd);
return 0;
}