2

I try to use libfuse (cuse) to create character device and play on it like with regular tty, all is fine till I use tcgetattr.

Unfortunately, termios.tcgetattr() always raise 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);
}

Here I use the same code (structure) in kernel module, and all is fine:

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

Result(for eg in 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']]
Danh
  • 5,916
  • 7
  • 30
  • 45
MrHetii
  • 1,315
  • 10
  • 12

1 Answers1

0

As far as I know, cuse drivers aren't like regular tty drivers because they aren't terminal drivers. They are user space character oriented file system drivers.

In order to play with it as you want, it should be under the tty components like below image (Taken from LDD3). Same source as the image describes how to create terminal derivers.

By the way, there is no userspace tty driver I know.

tty core overview

Ricardo Cristian Ramirez
  • 1,194
  • 3
  • 20
  • 42
  • 1
    Hi Ricardo. Thank your for comment but at the end of the day I wrote what I needed. It`s a bridge between virtual TTY character device and any TCP endpoint. Anyone who need it, can grab code from https://bitbucket.org/hetii/cusetty – MrHetii Sep 13 '17 at 17:59