CUSE - вернуть правильный IOCTL для termios.tcgetattr()
Я пытаюсь использовать libfuse
(cuse), чтобы создать символьное устройство и играть на нем, как с обычным tty, все хорошо, пока я не использую tcgetattr
,
К несчастью, termios.tcgetattr()
всегда поднимать I/O error
,
cusetest.c
#define FUSE_USE_VERSION 29
#define _FILE_OFFSET_BITS 64
#include <fuse/cuse_lowlevel.h>
#include <fuse/fuse_opt.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//#include <termios.h>
#include <linux/termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#define LOG(...) do { fprintf(stderr, "DEBUG: "__VA_ARGS__); puts(""); } while (0)
static void cusetest_open(fuse_req_t req, struct fuse_file_info *fi) {
LOG("cusetest_open called\n");
fuse_reply_open(req, fi);
}
static void cusetest_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_info *fi) {
LOG("cusetest_read called\n");
fuse_reply_buf(req, "Hello", size > 5 ? 5 : size);
}
static void cusetest_write(fuse_req_t req, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) {
LOG("cusetest_write called, size: %lu bytes\n", size);
fuse_reply_write(req, size);
}
static void cusetest_ioctl(fuse_req_t req, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz) {
LOG("cusetest_ioctl called, cmd: %d insize: %lu outsize: %lu\n", cmd, in_bufsz, out_bufsz);
struct termios oldtio;
int i;
oldtio.c_iflag = 1;
oldtio.c_oflag = 2;
oldtio.c_cflag = 3;
oldtio.c_lflag = 4;
for (i = 0; i < NCCS; ++i)
{
oldtio.c_cc[i] = i;
}
printf("NCCS:%ud\n\n", NCCS);
printf("c_iflag:%ud \n", oldtio.c_iflag);
printf("c_oflag:%ud\n", oldtio.c_oflag);
printf("c_cflag:%ud\n", oldtio.c_cflag);
printf("c_lflag:%ud\n", oldtio.c_lflag);
// printk("c_ispeed:%ud\n", oldtio.ispeed);
// printk("c_ospeed:%ud\n\n", oldtio.c_ospeed);
for (i = 0; i < NCCS; ++i)
{
printf("CC: %d\n", oldtio.c_cc[i]);
}
printf("\n");
fuse_reply_ioctl(req, 21506, &oldtio, sizeof(oldtio));
}
static const struct cuse_lowlevel_ops cusetest_clop = {
.open = cusetest_open,
.read = cusetest_read,
.write = cusetest_write,
.ioctl = cusetest_ioctl,
};
struct cuse_info2 {
unsigned int dev_major;
unsigned int dev_minor;
unsigned int dev_info_argc;
char ** dev_info_argv;
unsigned int flags;
};
// char * argv[] == char ** argv
int main(int argc, char** argv) {
// -f: run in foreground, -d: debug ouput
// Compile official example and use -h
const char* cusearg[] = {"test", "-f", "-d"};
const char* devarg[] = {"DEVNAME=ttyCUSE0" };
struct cuse_info ci;
memset(&ci, 0x00, sizeof(ci));
ci.flags = CUSE_UNRESTRICTED_IOCTL;
ci.dev_info_argc=1;
ci.dev_info_argv = devarg;
//int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, void *userdata);
return cuse_lowlevel_main(3, (char**) &cusearg, &ci, &cusetest_clop, NULL);
}
Здесь я использую тот же код (структуру) в модуле ядра, и все в порядке:
ttymodule.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/termios.h>
#include <linux/errno.h>
#define DEVICE_NAME "ttytest"
#define CLASS_NAME "ttytest"
static int major;
static struct class* tty_class = NULL;
static struct device* tty_device = NULL;
static long fake_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg){
struct termios oldtio;
int i;
oldtio.c_iflag = 1;
oldtio.c_oflag = 2;
oldtio.c_cflag = 3;
oldtio.c_lflag = 4;
for (i = 0; i < NCCS; ++i)
{
oldtio.c_cc[i] = i;
}
printk(KERN_ALERT "ttytest: ioctl called: %d -> %ld \n", cmd_in, arg);
printk(KERN_ALERT "NCCS:%ud\n\n", NCCS);
printk(KERN_ALERT "c_iflag:%ud \n", oldtio.c_iflag);
printk(KERN_ALERT "c_oflag:%ud\n", oldtio.c_oflag);
printk(KERN_ALERT "c_cflag:%ud\n", oldtio.c_cflag);
printk(KERN_ALERT "c_lflag:%ud\n", oldtio.c_lflag);
//printk(KERN_ALERT "c_ispeed:%ud\n", oldtio.ispeed);
//printk(KERN_ALERT "c_ospeed:%ud\n\n", oldtio.c_ospeed);
for (i = 0; i < NCCS; ++i)
{
printk(KERN_ALERT "CC: %d\n", oldtio.c_cc[i]);
}
printk(KERN_ALERT "\n");
return cmd_in+1;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.unlocked_ioctl = fake_ioctl
};
static int __init tty_init(void){
printk(KERN_INFO "ttytest: Initializing ...\n");
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major<0){
printk(KERN_ALERT "ttytest failed to register a major number\n");
return major;
}
tty_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(tty_class)){
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(tty_class);
}
tty_device = device_create(tty_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(tty_device)){
class_destroy(tty_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(tty_device);
}
return 0;
}
static void __exit tty_exit(void){
device_destroy(tty_class, MKDEV(major, 0));
class_unregister(tty_class); // unregister the device class
class_destroy(tty_class); // remove the device class
unregister_chrdev(major, DEVICE_NAME); // unregister the major number
printk(KERN_INFO "ttytest: Goodbye ...\n");
}
module_init(tty_init);
module_exit(tty_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Grzegorz Hetman");
MODULE_DESCRIPTION("A simple tty module to test cuse implementation.");
MODULE_VERSION("0.1");
Makefile:
obj-m := ttymodule.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all: unload clean
$(MAKE) -C $(KERNELDIR) M=$(PWD)
@make load
@make cuse
@sudo ./cusetest
clean:
@rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c *.order *.symvers cusetest
load:
@sudo insmod ttymodule.ko
@sudo chmod 777 /dev/ttytest
@sudo lsmod |grep ttymodule
unload:
@sudo rmmod ttymodule || true
@sudo rm -f /dev/ttymodule
cuse:
@gcc -Wall -g cusetest.c -lfuse -o cusetest
Результат (например, в Python):
import termios
termios.tcgetattr(open('/dev/ttyCUSE0','rw+')) # error: (5, 'Input/output error')
termios.tcgetattr(open('/dev/ttytest','rw+')) # [5523920, 0, 1576586344, 32702, 8, 8, ['\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '@', 'r', '\x90', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'g', '\x82', '\x01', '\x00', '\x00', '\x00', '\x00', ',', 'h', 'K', '\x00', '\x00', '\x00', '\x00', '\x00', '\x90']]
1 ответ
Насколько я знаю, драйверы cuse не похожи на обычные tty-драйверы, потому что они не являются терминальными драйверами. Это драйверы файловой системы, ориентированные на символьное пространство пользователя.
Чтобы играть с ним, как вы хотите, он должен находиться под tty-компонентами, как показано на рисунке ниже (взято из LDD3). Тот же источник, что и на изображении, описывает, как создавать терминальные производные.
Между прочим, я не знаю ни одного пользовательского драйвера tty.