From 2eebb9de411333628ce8fc5894f072b6ed6179e0 Mon Sep 17 00:00:00 2001 From: Jingxian He Date: Wed, 19 May 2021 21:55:34 +0800 Subject: [PATCH 25/72] char_dev: add support for char device dump and restore Add support for char device dump and restore during module upgrade. `/sys/kernel/repairing_device` provides the char device whiltelist with `IOCTL_CMD_{NEEDREPAIR, REPAIR}` command besides the internal device list. The device modules could use `mures_{add, del}_devname()` to add, or delete the char device whitelist dynamically. Signed-off-by: Xiaoguang Li Signed-off-by: Jingxian He Signed-off-by: fu.lin --- criu/Makefile.crtools | 2 + criu/config.c | 1 + criu/cr-dump.c | 4 ++ criu/cr-restore.c | 4 +- criu/crtools.c | 2 + criu/devname.c | 130 +++++++++++++++++++++++++++++++++++ criu/files-chr.c | 104 ++++++++++++++++++++++++++++ criu/files-reg.c | 6 +- criu/files.c | 93 ++++++++++++++++++++++++- criu/include/cr_options.h | 1 + criu/include/files-chr.h | 25 +++++++ criu/include/files.h | 6 ++ criu/include/image-desc.h | 1 + criu/include/image.h | 1 + criu/include/protobuf-desc.h | 1 + criu/mem.c | 7 +- criu/proc_parse.c | 21 +++++- images/Makefile | 1 + images/chr.proto | 12 ++++ images/fdinfo.proto | 3 + 20 files changed, 417 insertions(+), 8 deletions(-) create mode 100644 criu/devname.c create mode 100644 criu/files-chr.c create mode 100644 criu/include/files-chr.h create mode 100644 images/chr.proto diff --git a/criu/Makefile.crtools b/criu/Makefile.crtools index 98c4135..2e82912 100644 --- a/criu/Makefile.crtools +++ b/criu/Makefile.crtools @@ -91,6 +91,8 @@ obj-y += pie-util-vdso.o obj-y += vdso.o obj-y += timens.o obj-y += pin-mem.o +obj-y += devname.o +obj-y += files-chr.o obj-$(CONFIG_HAS_LIBBPF) += bpfmap.o obj-$(CONFIG_COMPAT) += pie-util-vdso-elf32.o CFLAGS_pie-util-vdso-elf32.o += -DCONFIG_VDSO_32 diff --git a/criu/config.c b/criu/config.c index 5d1cff6..03cad66 100644 --- a/criu/config.c +++ b/criu/config.c @@ -701,6 +701,7 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd, { "network-lock", required_argument, 0, 1100 }, BOOL_OPT("use-fork-pid", &opts.use_fork_pid), BOOL_OPT("with-notifier", &opts.with_notifier_kup), + BOOL_OPT("dump-char-dev", &opts.dump_char_dev), {}, }; diff --git a/criu/cr-dump.c b/criu/cr-dump.c index 50a2f9b..fd17413 100644 --- a/criu/cr-dump.c +++ b/criu/cr-dump.c @@ -88,6 +88,7 @@ #include "asm/dump.h" #include "pin-mem.h" #include "notifier.h" +#include "files-chr.h" /* * Architectures can overwrite this function to restore register sets that @@ -1880,6 +1881,9 @@ int cr_pre_dump_tasks(pid_t pid) */ rlimit_unlimit_nofile(); + if (opts.dump_char_dev && parse_devname() < 0) + goto err; + root_item = alloc_pstree_item(); if (!root_item) goto err; diff --git a/criu/cr-restore.c b/criu/cr-restore.c index b805265..2904a75 100644 --- a/criu/cr-restore.c +++ b/criu/cr-restore.c @@ -332,11 +332,11 @@ static int root_prepare_shared(void) if (pi->pid->state == TASK_HELPER) continue; - ret = prepare_mm_pid(pi); + ret = prepare_fd_pid(pi); if (ret < 0) break; - ret = prepare_fd_pid(pi); + ret = prepare_mm_pid(pi); if (ret < 0) break; diff --git a/criu/crtools.c b/criu/crtools.c index 1d08620..dc6d603 100644 --- a/criu/crtools.c +++ b/criu/crtools.c @@ -451,6 +451,8 @@ usage: " --use-fork-pid Allow to restore task pid by setting fork pid of task struct.\n" " --with-notifier Allow to checkpoint/restore kup notifier chain.\n" " This feature needs the kernel assistance.\n" + " --dump-char-dev Dump char dev files as normal file with repair cmd\n" + \ "\n" "Check options:\n" " Without options, \"criu check\" checks availability of absolutely required\n" diff --git a/criu/devname.c b/criu/devname.c new file mode 100644 index 0000000..5f6fbed --- /dev/null +++ b/criu/devname.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#include "log.h" +#include "common/xmalloc.h" + +#define REPAIRING_DEVICE_FILE "/sys/kernel/repairing_device" +#define ASCII_SIZE 128 + +static void *root_bucket[ASCII_SIZE]; + +static int insert_devname_internal(void *bucket[], const char *name) +{ + void *new = NULL; + int idx = *name; + + if (bucket[idx] != NULL) + return insert_devname_internal(bucket[idx], name+1); + else if (idx == '\0') { + new = xmalloc(sizeof(void *)); + if (!new) { + pr_perror("alloc devname failed\n"); + return -1; + } + bucket[idx] = new; + return 0; + } else { + new = xmalloc(sizeof(void *) * ASCII_SIZE); + if (!new) { + pr_perror("alloc devname failed\n"); + return -1; + } + memset(new, 0, sizeof(void *) * ASCII_SIZE); + bucket[idx] = new; + return insert_devname_internal(bucket[idx], name+1); + } +} + +int insert_devname(const char *devname) +{ + if (devname == NULL || *devname == '\0') // ignore + return 0; + + pr_debug("insert device '%s'\n", devname); + return insert_devname_internal(root_bucket, devname); +} + +int parse_devname(void) +{ + int retval = -1; + char *line = NULL; + size_t len = 0; + ssize_t nread = 0; + FILE *fp = NULL; + + fp = fopen(REPAIRING_DEVICE_FILE, "r"); + if (fp == NULL) { + pr_info("Unable to open %s, downgrade to use internal whitelist\n", + REPAIRING_DEVICE_FILE); + return 0; + } + + while ((nread = getline(&line, &len, fp)) != -1) { + if (nread <= 1) // ignore empty string + continue; + + line[nread-1] = '\0'; // drop '\n' + retval = insert_devname(line); + if (retval != 0) + goto out; + } + retval = 0; + +out: + free(line); + fclose(fp); + return retval; +} + +static const char *steal_devname(const char *name, ssize_t len) +{ + ssize_t off = len; + + for (off -= 1; off > 0; off--) { + if (name[off] == '/') + break; + } + + return name + off + 1; +} + +static bool find_devname_internal(void *bucket[], const char *name) +{ + int idx = *name; + + if (*name == '\0' && bucket[idx] != NULL) + return true; + else if (bucket[idx] == NULL) + return false; + else { + return find_devname_internal(bucket[idx], name+1); + } +} + +bool find_devname(const char *name) +{ + const char *devname; + size_t len = 0; + bool found = false; + + if (name == NULL) + return false; + else if ((len = strlen(name)) == 0) + return false; + + devname = steal_devname(name, len); + found = find_devname_internal(root_bucket, devname); + + pr_debug("device '%s' (original name '%s') %s found in %s\n", + devname, name, found ? "is" : "isn't", REPAIRING_DEVICE_FILE); + + /* Compatible with the old version, there are still `strstr` branch in the following */ + found |= (strstr(name, "uverbs") != NULL + || strstr(name, "rdma_cm") != NULL + || strstr(name, "umad") != NULL); + + return found; +} diff --git a/criu/files-chr.c b/criu/files-chr.c new file mode 100644 index 0000000..2eb023e --- /dev/null +++ b/criu/files-chr.c @@ -0,0 +1,104 @@ +#include + +#include "imgset.h" +#include "pstree.h" +#include "files-chr.h" +#include "log.h" + +#include "protobuf.h" + +/* Checks if file descriptor @lfd is infinibandevent */ +int is_infiniband_link(char *link) +{ + return is_anon_link_type(link, "[infinibandevent]"); +} + +static int chrfile_open(struct file_desc *d, int *new_fd) +{ + int fd, mntns_root; + int ret = 0; + struct chrfile_info *ci; + + ci = container_of(d, struct chrfile_info, d); + + if (ci->cfe->repair) + ci->cfe->flags |= O_REPAIR; + + mntns_root = open_pid_proc(getpid()); + fd = openat(mntns_root, ci->path, ci->cfe->flags); + if (fd < 0){ + pr_err("open chr file failed\n"); + return -1; + } + + if (ci->cfe->repair) { + ret = ioctl(fd, IOCTL_CMD_REPAIR , ci->cfe->index); + pr_info("repair ioctl return: %d, index: %d\n", ret, ci->cfe->index); + if (ret) + goto err; + } + + *new_fd = fd; + return ret; +err: + close(fd); + return ret; +} + +static struct file_desc_ops chrfile_desc_ops = { + .type = FD_TYPES__CHR, + .open = chrfile_open, +}; + +static int collect_one_chrfile(void *o, ProtobufCMessage *base, struct cr_img *i) +{ + struct chrfile_info *ci = o; + static char dot[] = "."; + + ci->cfe = pb_msg(base, ChrfileEntry); + if (ci->cfe->name[1] == '\0') + ci->path = dot; + else + ci->path = ci->cfe->name; + + pr_info("Collected chr file: %#x, name: %s\n", ci->cfe->id, ci->path); + file_desc_add(&ci->d, ci->cfe->id, &chrfile_desc_ops); + + return 0; +} + +struct collect_image_info chrfile_cinfo = { + .fd_type = CR_FD_CHRFILE, + .pb_type = PB_CHRFILE, + .priv_size = sizeof(struct chrfile_info), + .collect = collect_one_chrfile, +}; + +int collect_chr_map(struct pstree_item *me, struct vma_area *vma) +{ + struct list_head *list = &rsti(me)->fds; + struct fdinfo_list_entry *fle, *tmp; + struct chrfile_info *ci; + bool exist_fd; + + + list_for_each_entry_safe(fle, tmp, list, ps_list) { + struct file_desc *d = fle->desc; + + if (d->ops->type != FD_TYPES__CHR) + continue; + + ci = container_of(d, struct chrfile_info, d); + if (!strcmp(vma->e->name, ci->path)) { + vma->vmfd = d; + vma->e->fd = fle->fe->fd; + exist_fd = true; + break; + } + } + + if (!exist_fd) + return -EEXIST; + + return 0; +} diff --git a/criu/files-reg.c b/criu/files-reg.c index fbdf811..b9576a4 100644 --- a/criu/files-reg.c +++ b/criu/files-reg.c @@ -45,6 +45,7 @@ #include "fault-injection.h" #include "external.h" #include "memfd.h" +#include "files-chr.h" #include "protobuf.h" #include "util.h" @@ -1640,7 +1641,8 @@ int dump_one_reg_file(int lfd, u32 id, const struct fd_parms *p) rfe.has_mnt_id = true; } - pr_info("Dumping path for %d fd via self %d [%s]\n", p->fd, lfd, &link->name[1]); + pr_info("Dumping path for %d fd via self %d [%s], id: %d\n", + p->fd, lfd, &link->name[1], id); /* * The regular path we can handle should start with slash. @@ -2373,7 +2375,7 @@ static int collect_one_regfile(void *o, ProtobufCMessage *base, struct cr_img *i rfi->remap = NULL; rfi->size_mode_checked = false; - pr_info("Collected [%s] ID %#x\n", rfi->path, rfi->rfe->id); + pr_info("Collected regfile [%s] ID %#x\n", rfi->path, rfi->rfe->id); return file_desc_add(&rfi->d, rfi->rfe->id, ®_desc_ops); } diff --git a/criu/files.c b/criu/files.c index f262d80..e1681a1 100644 --- a/criu/files.c +++ b/criu/files.c @@ -49,6 +49,7 @@ #include "kerndat.h" #include "fdstore.h" #include "bpfmap.h" +#include "files-chr.h" #include "protobuf.h" #include "util.h" @@ -325,10 +326,32 @@ int do_dump_gen_file(struct fd_parms *p, int lfd, const struct fdtype_ops *ops, e->fd = p->fd; e->flags = p->fd_flags; + pr_info("fdinfoEntry fd: %d\n", e->fd); ret = fd_id_generate(p->pid, e, p); if (ret == 1) /* new ID generated */ ret = ops->dump(lfd, e->id, p); - else + else if (ops->type == FD_TYPES__CHR) { + /* + * Sometimes the app_data subprocess may inherit the fd from + * app_data. Those fds may result the unconditional oops during + * the restoration of app_data. Therefore, prevent the dump in + * those condition. + */ + struct fd_link _link, *link; + + if (!p->link) { + if (fill_fdlink(lfd, p, &_link)) + return -1; + link = &_link; + } else + link = p->link; + + if (find_devname(link->name)) { + pr_err("char dev '%s' fd %d is owned by multi-processes\n", + link->name, e->fd); + ret = -1; + } + } else /* Remove locks generated by the fd before going to the next */ discard_dup_locks_tail(p->pid, e->fd); @@ -466,6 +489,58 @@ static int dump_blkdev(struct fd_parms *p, int lfd, FdinfoEntry *e) return err; } +static int dump_chr_file(int lfd, u32 id, const struct fd_parms *p) +{ + int ret; + struct fd_link _link, *link; + struct cr_img *img; + FileEntry fe = FILE_ENTRY__INIT; + ChrfileEntry cfe = CHRFILE_ENTRY__INIT; + + if (!p->link) { + if (fill_fdlink(lfd, p, &_link)) + return -1; + link = &_link; + } else + link = p->link; + + pr_info("Dumping chr-file fd %d with lfd %d with id %d, name: %s\n", p->fd, lfd, id, link->name); + + if (strstr(link->name, "(deleted)") != NULL) { + pr_err("char device '%s' is deleted\n", link->name); + return -ENXIO; + } + + cfe.repair = false; + if (find_devname(link->name)) { + ret = ioctl(lfd, IOCTL_CMD_NEEDREPAIR, 0); + if (ret <= 0) { + pr_err("ioctl cmd needrepair failed, errno: %d, %s\n", ret, strerror(errno)); + return -1; + } else { + pr_info("char device needrepair cmd return: %d\n", ret); + cfe.index = ret; + cfe.repair = true; + } + } + + cfe.id = id; + cfe.name = &link->name[1]; + cfe.flags = p->flags; + fe.type = FD_TYPES__CHR; + fe.id = cfe.id; + fe.chr = &cfe; + + img = img_from_set(glob_imgset, CR_FD_FILES); + ret = pb_write_one(img, &fe, PB_FILE); + return ret; +} + +const struct fdtype_ops chr_dump_ops = { + .type = FD_TYPES__CHR, + .dump = dump_chr_file, +}; + static int dump_chrdev(struct fd_parms *p, int lfd, FdinfoEntry *e) { struct fd_link *link_old = p->link; @@ -493,6 +568,10 @@ static int dump_chrdev(struct fd_parms *p, int lfd, FdinfoEntry *e) ops = &tty_dump_ops; break; } + if (opts.dump_char_dev) { + ops = &chr_dump_ops; + break; + } sprintf(more, "%d:%d", maj, minor(p->stat.st_rdev)); err = dump_unsupp_fd(p, lfd, "chr", more, e); @@ -559,6 +638,8 @@ static int dump_one_file(struct pid *pid, int fd, int lfd, struct fd_opts *opts, ops = &signalfd_dump_ops; else if (is_timerfd_link(link)) ops = &timerfd_dump_ops; + else if (is_infiniband_link(link)) + return 1; #ifdef CONFIG_HAS_LIBBPF else if (is_bpfmap_link(link)) ops = &bpfmap_dump_ops; @@ -663,6 +744,11 @@ int dump_task_files_seized(struct parasite_ctl *ctl, struct pstree_item *item, s ret = dump_one_file(item->pid, dfds->fds[i + off], lfds[i], opts + i, ctl, &e, dfds); if (ret) break; + /* infiniband link file */ + if (ret > 0) { + ret = 0; + continue; + } ret = pb_write_one(img, &e, PB_FDINFO); if (ret) @@ -917,6 +1003,7 @@ int prepare_fd_pid(struct pstree_item *item) if (!img) return -1; + pr_info("prepare_fd_pid\n"); while (1) { FdinfoEntry *e; @@ -1125,6 +1212,7 @@ int setup_and_serve_out(struct fdinfo_list_entry *fle, int new_fd) if (reopen_fd_as(fle->fe->fd, new_fd)) return -1; + pr_info("*******flags: %d",fle->fe->flags); if (fcntl(fle->fe->fd, F_SETFD, fle->fe->flags) == -1) { pr_perror("Unable to set file descriptor flags"); return -1; @@ -1761,6 +1849,9 @@ static int collect_one_file(void *o, ProtobufCMessage *base, struct cr_img *i) ret = collect_one_file_entry(fe, fe->bpf->id, &fe->bpf->base, &bpfmap_cinfo); break; #endif + case FD_TYPES__CHR: + ret = collect_one_file_entry(fe, fe->chr->id, &fe->chr->base, &chrfile_cinfo); + break; } return ret; diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h index 039edba..226acb2 100644 --- a/criu/include/cr_options.h +++ b/criu/include/cr_options.h @@ -193,6 +193,7 @@ struct cr_options { int pin_memory; int use_fork_pid; int with_notifier_kup; + int dump_char_dev; }; extern struct cr_options opts; diff --git a/criu/include/files-chr.h b/criu/include/files-chr.h new file mode 100644 index 0000000..5be11f5 --- /dev/null +++ b/criu/include/files-chr.h @@ -0,0 +1,25 @@ +#ifndef __CRIU_FILES_CHR_H__ +#define __CRIU_FILES_CHR_H__ + +#include "files.h" + +#include "images/chr.pb-c.h" + +struct chrfile_info { + struct file_desc d; + ChrfileEntry *cfe; + char *path; +}; + +extern struct collect_image_info chrfile_cinfo; + +extern const struct fdtype_ops chr_dump_ops; +extern int collect_chr_map(struct pstree_item *me, struct vma_area *); + +int parse_devname(void); +bool find_devname(const char *name); + +int collect_chr_map(struct pstree_item *me, struct vma_area *vma); +int is_infiniband_link(char *link); + +#endif /* __CRIU_FILES_CHR_H__ */ diff --git a/criu/include/files.h b/criu/include/files.h index 96face7..1d979a9 100644 --- a/criu/include/files.h +++ b/criu/include/files.h @@ -15,6 +15,12 @@ #include "images/fown.pb-c.h" #include "images/vma.pb-c.h" +#ifndef IOCTL_CMD_NEEDREPAIR +#define IOCTL_CMD_NEEDREPAIR 0x00100000UL +#define IOCTL_CMD_REPAIR 0x00200000UL +#define O_REPAIR 040000000 +#endif + struct parasite_drain_fd; struct pstree_item; struct file_desc; diff --git a/criu/include/image-desc.h b/criu/include/image-desc.h index 5045bae..e35f8b2 100644 --- a/criu/include/image-desc.h +++ b/criu/include/image-desc.h @@ -115,6 +115,7 @@ enum { CR_FD_MEMFD_FILE, CR_FD_AUTOFS, + CR_FD_CHRFILE, CR_FD_MAX }; diff --git a/criu/include/image.h b/criu/include/image.h index f598de7..66492c0 100644 --- a/criu/include/image.h +++ b/criu/include/image.h @@ -85,6 +85,7 @@ #define VMA_AREA_AIORING (1 << 13) #define VMA_AREA_MEMFD (1 << 14) #define VMA_AREA_ANON_INODE (1 << 15) +#define VMA_AREA_CHR (1 << 16) #define VMA_CLOSE (1 << 28) #define VMA_NO_PROT_WRITE (1 << 29) diff --git a/criu/include/protobuf-desc.h b/criu/include/protobuf-desc.h index 3824de1..2468e8f 100644 --- a/criu/include/protobuf-desc.h +++ b/criu/include/protobuf-desc.h @@ -70,6 +70,7 @@ enum { PB_BPFMAP_FILE, PB_BPFMAP_DATA, PB_APPARMOR, + PB_CHRFILE, /* PB_AUTOGEN_STOP */ diff --git a/criu/mem.c b/criu/mem.c index 00965f0..b955d66 100644 --- a/criu/mem.c +++ b/criu/mem.c @@ -32,6 +32,7 @@ #include "compel/infect-util.h" #include "pidfd-store.h" #include "pin-mem.h" +#include "files-chr.h" #include "protobuf.h" #include "images/pagemap.pb-c.h" @@ -717,7 +718,9 @@ int prepare_mm_pid(struct pstree_item *i) pr_info("vma 0x%" PRIx64 " 0x%" PRIx64 "\n", vma->e->start, vma->e->end); - if (vma_area_is(vma, VMA_ANON_SHARED)) + if (vma_area_is(vma, VMA_AREA_CHR)) + ret = collect_chr_map(i, vma); + else if (vma_area_is(vma, VMA_ANON_SHARED)) ret = collect_shmem(pid, vma); else if (vma_area_is(vma, VMA_FILE_PRIVATE) || vma_area_is(vma, VMA_FILE_SHARED)) ret = collect_filemap(vma); @@ -1358,7 +1361,7 @@ int open_vmas(struct pstree_item *t) filemap_ctx_init(false); list_for_each_entry(vma, &vmas->h, list) { - if (vma_area_is(vma, VMA_AREA_ANON_INODE)) + if (vma_area_is(vma, VMA_AREA_ANON_INODE) || vma_area_is(vma, VMA_AREA_CHR)) continue; if (!vma_area_is(vma, VMA_AREA_REGULAR) || !vma->vm_open) diff --git a/criu/proc_parse.c b/criu/proc_parse.c index e41d43a..8913d93 100644 --- a/criu/proc_parse.c +++ b/criu/proc_parse.c @@ -603,11 +603,30 @@ static int handle_vma(pid_t pid, struct vma_area *vma_area, const char *file_pat } else if (*vm_file_fd >= 0) { struct stat *st_buf = vma_area->vmst; + pr_info("file mode is: %x, st_ino: %ld\n", + st_buf->st_mode, st_buf->st_ino); if (S_ISREG(st_buf->st_mode)) /* regular file mapping -- supported */; else if (S_ISCHR(st_buf->st_mode) && (st_buf->st_rdev == DEVZERO)) /* devzero mapping -- also makes sense */; - else { + else if (S_ISCHR(st_buf->st_mode) && opts.dump_char_dev) { + /* NOTICE: if `--dump-char-dev` option is set, permmit + * all char device memory area dumping. + */ + if (strstr(file_path, "uverbs") != NULL) { + int len = strlen(file_path) + 1; + + vma_area->e->status |= VMA_AREA_CHR; + vma_area->e->name = xmalloc(len); + if (!vma_area->e->name) { + pr_err("alloc vma area name failed\n"); + goto err; + strncpy(vma_area->e->name, file_path, len); + pr_info("vma name content is: %s\n", + vma_area->e->name); + } + } + } else { pr_err("Can't handle non-regular mapping on %d's map %" PRIx64 "\n", pid, vma_area->e->start); goto err; } diff --git a/images/Makefile b/images/Makefile index 004e22e..37dff9a 100644 --- a/images/Makefile +++ b/images/Makefile @@ -72,6 +72,7 @@ proto-obj-y += bpfmap-file.o proto-obj-y += bpfmap-data.o proto-obj-y += apparmor.o proto-obj-y += rseq.o +proto-obj-y += chr.o CFLAGS += -iquote $(obj)/ diff --git a/images/chr.proto b/images/chr.proto new file mode 100644 index 0000000..67929db --- /dev/null +++ b/images/chr.proto @@ -0,0 +1,12 @@ +syntax = "proto2"; + +import "opts.proto"; + +message chrfile_entry { + required uint32 id = 1; + required uint32 flags = 2 [(criu).flags = "rfile.flags"]; + required uint32 index = 3; + required string name = 4; + required bool repair = 5; +}; + diff --git a/images/fdinfo.proto b/images/fdinfo.proto index 88f1c11..6549472 100644 --- a/images/fdinfo.proto +++ b/images/fdinfo.proto @@ -20,6 +20,7 @@ import "pipe.proto"; import "tty.proto"; import "memfd.proto"; import "bpfmap-file.proto"; +import "chr.proto"; enum fd_types { UND = 0; @@ -42,6 +43,7 @@ enum fd_types { TIMERFD = 17; MEMFD = 18; BPFMAP = 19; + CHR = 21; /* Any number above the real used. Not stored to image */ CTL_TTY = 65534; @@ -78,4 +80,5 @@ message file_entry { optional tty_file_entry tty = 19; optional memfd_file_entry memfd = 20; optional bpfmap_file_entry bpf = 21; + optional chrfile_entry chr = 23; } -- 2.34.1