/* PTYFS - file system for Unix98 pseudoterminal slave nodes (/dev/pts) */ #include #include #include #include #include #include #include "node.h" #define ROOT_INO_NR 1 /* inode number of the root directory */ #define BASE_INO_NR 2 /* first inode number for slave nodes */ #define GETDENTS_BUF 1024 /* size of the temporary buffer for getdents */ static struct node_data root_data = { .mode = S_IFDIR | 0755, .uid = 0, .gid = 0, .dev = NO_DEV }; /* * Mount the file system. */ static int ptyfs_mount(dev_t __unused dev, unsigned int flags, struct fsdriver_node * root_node, unsigned int * res_flags) { /* This file system can not be used as a root file system. */ if (flags & REQ_ISROOT) return EINVAL; /* Return the details of the root node. */ root_node->fn_ino_nr = ROOT_INO_NR; root_node->fn_mode = root_data.mode; root_node->fn_uid = root_data.uid; root_node->fn_gid = root_data.gid; root_node->fn_size = 0; root_node->fn_dev = root_data.dev; *res_flags = RES_NOFLAGS; return OK; } /* * Generate the name string of a slave node based on its node number. Return * OK on success, with the null-terminated name stored in the buffer 'name' * which is 'size' bytes in size. Return an error code on failure. */ static int make_name(char * name, size_t size, node_t index) { ssize_t r; if ((r = snprintf(name, sizeof(name), "%u", index)) < 0) return EINVAL; if (r >= size) return ENAMETOOLONG; return OK; } /* * Parse the name of a slave node as given by a user, and check whether it is a * valid slave node number. A valid slave number is any name that can be * produced by make_name(). Return TRUE if the string was successfully parsed * as a slave node number (which may or may not actually be allocated), with * the number stored in 'indexp'. Return FALSE if the name is not a number. */ static int parse_name(const char * name, node_t * indexp) { node_t index; const char *p; index = 0; for (p = name; *p; p++) { /* Digits only. */ if (*p < '0' || *p > '9') return FALSE; /* No leading zeroes. */ if (p != name && index == 0) return FALSE; /* No overflow. */ if (index * 10 < index) return FALSE; index = index * 10 + *p - '0'; } *indexp = index; return TRUE; } /* * Look up a name in a directory, yielding a node on success. For a successful * lookup, the given name must either be a single dot, which resolves to the * file system root directory, or the number of an allocated slave node. */ static int ptyfs_lookup(ino_t dir_nr, char * name, struct fsdriver_node * node, int * is_mountpt) { struct node_data *data; node_t index; ino_t ino_nr; assert(name[0] != '\0'); if (dir_nr != ROOT_INO_NR) return ENOENT; if (name[0] == '.' && name[1] == '\0') { /* The root directory itself is requested. */ ino_nr = ROOT_INO_NR; data = &root_data; } else { /* Parse the user-provided name, which must be a number. */ if (!parse_name(name, &index)) return ENOENT; ino_nr = BASE_INO_NR + index; /* See if the number is in use, and get its details. */ if ((data = get_node(index)) == NULL) return ENOENT; } node->fn_ino_nr = ino_nr; node->fn_mode = data->mode; node->fn_uid = data->uid; node->fn_gid = data->gid; node->fn_size = 0; node->fn_dev = data->dev; *is_mountpt = FALSE; return OK; } /* * Enumerate directory contents. */ static ssize_t ptyfs_getdents(ino_t ino_nr, struct fsdriver_data * data, size_t bytes, off_t * posp) { struct fsdriver_dentry fsdentry; static char buf[GETDENTS_BUF]; char name[NAME_MAX + 1]; struct node_data *node_data; unsigned int type; off_t pos; node_t index; ssize_t r; if (ino_nr != ROOT_INO_NR) return EINVAL; fsdriver_dentry_init(&fsdentry, data, bytes, buf, sizeof(buf)); for (;;) { pos = (*posp)++; if (pos < 2) { strlcpy(name, (pos == 0) ? "." : "..", sizeof(name)); ino_nr = ROOT_INO_NR; type = DT_DIR; } else { if (pos - 2 >= get_max_node()) break; /* EOF */ index = (node_t)(pos - 2); if ((node_data = get_node(index)) == NULL) continue; /* index not in use */ if (make_name(name, sizeof(name), index) != OK) continue; /* could not generate name string */ ino_nr = BASE_INO_NR + index; type = IFTODT(node_data->mode); } if ((r = fsdriver_dentry_add(&fsdentry, ino_nr, name, strlen(name), type)) < 0) return r; if (r == 0) break; /* result buffer full */ } return fsdriver_dentry_finish(&fsdentry); } /* * Return a pointer to the node data structure for the given inode number, or * NULL if no node exists for the given inode number. */ static struct node_data * get_data(ino_t ino_nr) { node_t index; if (ino_nr == ROOT_INO_NR) return &root_data; if (ino_nr < BASE_INO_NR || ino_nr >= BASE_INO_NR + get_max_node()) return NULL; index = (node_t)(ino_nr - BASE_INO_NR); return get_node(index); } /* * Change file ownership. */ static int ptyfs_chown(ino_t ino_nr, uid_t uid, gid_t gid, mode_t * mode) { struct node_data *data; if ((data = get_data(ino_nr)) == NULL) return EINVAL; data->uid = uid; data->gid = gid; data->mode &= ~(S_ISUID | S_ISGID); *mode = data->mode; return OK; } /* * Change file mode. */ static int ptyfs_chmod(ino_t ino_nr, mode_t * mode) { struct node_data *data; if ((data = get_data(ino_nr)) == NULL) return EINVAL; data->mode = (data->mode & ~ALLPERMS) | (*mode & ALLPERMS); *mode = data->mode; return OK; } /* * Return node details. */ static int ptyfs_stat(ino_t ino_nr, struct stat * buf) { struct node_data *data; if ((data = get_data(ino_nr)) == NULL) return EINVAL; buf->st_mode = data->mode; buf->st_uid = data->uid; buf->st_gid = data->gid; buf->st_nlink = S_ISDIR(data->mode) ? 2 : 1; buf->st_rdev = data->dev; buf->st_atime = data->ctime; buf->st_mtime = data->ctime; buf->st_ctime = data->ctime; return OK; } /* * Return file system statistics. */ static int ptyfs_statvfs(struct statvfs * buf) { buf->f_flag = ST_NOTRUNC; buf->f_namemax = NAME_MAX; return OK; } /* * Process non-filesystem messages, in particular slave node creation and * deletion requests from the PTY service. */ static void ptyfs_other(const message * m_ptr, int ipc_status) { char label[DS_MAX_KEYLEN]; struct node_data data; message m_reply; int r; /* * We only accept requests from the service with the label "pty". * More sophisticated access checks are part of future work. */ if ((r = ds_retrieve_label_name(label, m_ptr->m_source)) != OK) { printf("PTYFS: unable to obtain label for %u (%d)\n", m_ptr->m_source, r); return; } if (strcmp(label, "pty")) { printf("PTYFS: unexpected request %x from %s/%u\n", m_ptr->m_type, label, m_ptr->m_source); return; } /* Process the request from PTY. */ memset(&m_reply, 0, sizeof(m_reply)); switch (m_ptr->m_type) { case PTYFS_SET: memset(&data, 0, sizeof(data)); data.dev = m_ptr->m_pty_ptyfs_req.dev; data.mode = m_ptr->m_pty_ptyfs_req.mode; data.uid = m_ptr->m_pty_ptyfs_req.uid; data.gid = m_ptr->m_pty_ptyfs_req.gid; data.ctime = clock_time(NULL); r = set_node(m_ptr->m_pty_ptyfs_req.index, &data); break; case PTYFS_CLEAR: clear_node(m_ptr->m_pty_ptyfs_req.index); r = OK; break; case PTYFS_NAME: r = make_name(m_reply.m_ptyfs_pty_name.name, sizeof(m_reply.m_ptyfs_pty_name.name), m_ptr->m_pty_ptyfs_req.index); break; default: printf("PTYFS: invalid request %x from PTY\n", m_ptr->m_type); r = ENOSYS; } /* * Send a reply to the request. In particular slave node addition * requests must be blocking for the PTY service, so as to avoid race * conditions between PTYFS creating the slave node and userland trying * to open it. */ m_reply.m_type = r; if (IPC_STATUS_CALL(ipc_status) == SENDREC) r = ipc_sendnb(m_ptr->m_source, &m_reply); else r = asynsend3(m_ptr->m_source, &m_reply, AMF_NOREPLY); if (r != OK) printf("PTYFS: unable to reply to PTY (%d)\n", r); } /* * Initialize the service. */ static int ptyfs_init(int __unused type, sef_init_info_t * __unused info) { init_nodes(); root_data.ctime = clock_time(NULL); return OK; } /* * Process an incoming signal. */ static void ptyfs_signal(int sig) { if (sig == SIGTERM) fsdriver_terminate(); } /* * Perform SEF initialization. */ static void ptyfs_startup(void) { sef_setcb_init_fresh(ptyfs_init); sef_setcb_signal_handler(ptyfs_signal); sef_startup(); } static struct fsdriver ptyfs_table = { .fdr_mount = ptyfs_mount, .fdr_lookup = ptyfs_lookup, .fdr_getdents = ptyfs_getdents, .fdr_stat = ptyfs_stat, .fdr_chown = ptyfs_chown, .fdr_chmod = ptyfs_chmod, .fdr_statvfs = ptyfs_statvfs, .fdr_other = ptyfs_other }; /* * The PTYFS service. */ int main(void) { ptyfs_startup(); fsdriver_task(&ptyfs_table); return 0; }