static void script_record_open(int fd) { dev_t fd_dev; const char *logname, *recording_path; const void* data; enum script_record_format fmt; if (!script_dev_logfile_map_inited) init_script_dev_logfile_map(); /* check if the opened device is one we want to record */ fd_dev = dev_of_fd(fd); if (!fd_map_get(&script_dev_logfile_map, fd_dev, (const void **)&logname)) { DBG(DBG_SCRIPT, "script_record_open: fd %i on device %i:%i is not recorded\n", fd, major(fd_dev), minor(fd_dev)); return; } assert (fd_map_get(&script_dev_devpath_map, fd_dev, (const void **)&recording_path)); assert (fd_map_get(&script_dev_format_map, fd_dev, &data)); fmt = (enum script_record_format) data; DBG(DBG_SCRIPT, "script_record_open: start recording fd %i on device %i:%i into %s (format %i)\n", fd, major(fd_dev), minor(fd_dev), logname, fmt); script_start_record(fd, logname, recording_path, fmt); }
static void script_start_record(int fd, const char *logname) { FILE *log; struct script_record_info *srinfo; if (fd_map_get(&script_recorded_fds, fd, NULL)) { fprintf(stderr, "script_record_open: internal error: fd %i is already being recorded\n", fd); abort(); } log = fopen(logname, "a"); if (log == NULL) { perror("umockdev: failed to open script record file"); exit(1); } /* if we have a previous record, make sure that we start a new line */ if (ftell(log) > 0) putc('\n', log); srinfo = malloc(sizeof(struct script_record_info)); srinfo->log = log; assert(clock_gettime(CLOCK_MONOTONIC, &srinfo->time) == 0); srinfo->op = 0; fd_map_add(&script_recorded_fds, fd, srinfo); }
static int ioctl_emulate(int fd, unsigned long request, void *arg) { ioctl_tree *ret; int ioctl_result = -1; int orig_errno; struct ioctl_fd_info *fdinfo; if (fd_map_get(&ioctl_wrapped_fds, fd, (const void **)&fdinfo)) { /* we default to erroring and an appropriate error code before * tree_execute, as handlers might change errno; if they succeed, we * reset errno */ orig_errno = errno; /* evdev ioctls default to ENOENT; FIXME: record that instead of * hardcoding, and handle in ioctl_tree */ if (_IOC_TYPE(request) == 'E') errno = ENOENT; else errno = ENOTTY; /* check our ioctl tree */ ret = ioctl_tree_execute(fdinfo->tree, fdinfo->last, request, arg, &ioctl_result); DBG(DBG_IOCTL, "ioctl_emulate: tree execute ret %p, result %i, errno %i (%m); orig errno: %i\n", ret, ioctl_result, errno, orig_errno); if (ret != NULL) fdinfo->last = ret; if (ioctl_result != -1 && errno != 0) errno = orig_errno; } else { ioctl_result = UNHANDLED; } return ioctl_result; }
static void netlink_close(int fd) { if (fd_map_get(&wrapped_netlink_sockets, fd, NULL)) { DBG(DBG_NETLINK, "netlink_close(): closing netlink socket fd %i\n", fd); fd_map_remove(&wrapped_netlink_sockets, fd); } }
static void ioctl_emulate_close(int fd) { struct ioctl_fd_info *fdinfo; if (fd_map_get(&ioctl_wrapped_fds, fd, (const void **)&fdinfo)) { DBG(DBG_IOCTL, "ioctl_emulate_close: closing ioctl socket fd %i\n", fd); fd_map_remove(&ioctl_wrapped_fds, fd); ioctl_tree_free(fdinfo->tree); free(fdinfo); } }
static void script_record_close(int fd) { libc_func(fclose, int, FILE *); struct script_record_info *srinfo; if (!fd_map_get(&script_recorded_fds, fd, (const void **)&srinfo)) return; DBG(DBG_SCRIPT, "script_record_close: stop recording fd %i\n", fd); _fclose(srinfo->log); free(srinfo); fd_map_remove(&script_recorded_fds, fd); }
static void script_record_op(char op, int fd, const void *buf, ssize_t size) { struct script_record_info *srinfo; unsigned long delta; libc_func(fwrite, size_t, const void *, size_t, size_t, FILE *); static char header[100]; const unsigned char *cur; int i; if (!fd_map_get(&script_recorded_fds, fd, (const void **)&srinfo)) return; if (size <= 0) return; DBG("script_record_op %c: got %zi bytes on fd %i\n", op, size, fd); delta = update_msec(&srinfo->time); DBG(" %lu ms since last operation %c\n", delta, srinfo->op); /* for negligible time deltas, append to the previous stanza, otherwise * create a new record */ if (delta >= 10 || srinfo->op != op) { if (srinfo->op != 0) putc('\n', srinfo->log); snprintf(header, sizeof(header), "%c %lu ", op, delta); assert(_fwrite(header, strlen(header), 1, srinfo->log) == 1); } /* escape ASCII control chars */ for (i = 0, cur = buf; i < size; ++i, ++cur) { if (*cur < 32) { putc('^', srinfo->log); putc(*cur + 64, srinfo->log); continue; } if (*cur == '^') { /* we cannot encode ^ as ^^, as we need that for 0x1E already; so * take the next free code which is 0x60 */ putc('^', srinfo->log); putc('`', srinfo->log); continue; } putc(*cur, srinfo->log); } fflush(srinfo->log); srinfo->op = op; }
static int ioctl_emulate(int fd, unsigned long request, void *arg) { ioctl_tree *ret; int ioctl_result = -2; struct ioctl_fd_info *fdinfo; if (fd_map_get(&ioctl_wrapped_fds, fd, (const void **)&fdinfo)) { /* check our ioctl tree */ ret = ioctl_tree_execute(fdinfo->tree, fdinfo->last, request, arg, &ioctl_result); if (ret != NULL) fdinfo->last = ret; } /* -2 means "unhandled" */ return ioctl_result; }
static void script_record_open(int fd) { dev_t fd_dev; const char *logname; if (!script_dev_logfile_map_inited) init_script_dev_logfile_map(); /* check if the opened device is one we want to record */ fd_dev = dev_of_fd(fd); if (!fd_map_get(&script_dev_logfile_map, fd_dev, (const void **)&logname)) { DBG("script_record_open: fd %i on device %i:%i is not recorded\n", fd, major(fd_dev), minor(fd_dev)); return; } DBG("script_record_open: start recording fd %i on device %i:%i into %s\n", fd, major(fd_dev), minor(fd_dev), logname); script_start_record(fd, logname); }
static int netlink_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { libc_func(bind, int, int, const struct sockaddr *, socklen_t); struct sockaddr_un sa; const char *path = getenv("UMOCKDEV_DIR"); if (fd_map_get(&wrapped_netlink_sockets, sockfd, NULL) && path != NULL) { DBG(DBG_NETLINK, "testbed wrapped bind: intercepting netlink socket fd %i\n", sockfd); /* we create one socket per fd, and send emulated uevents to all of * them; poor man's multicast; this can become more elegant if/when * AF_UNIX multicast lands */ sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/event%i", path, sockfd); /* clean up from previously closed fds, to avoid "already in use" error */ unlink(sa.sun_path); return _bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)); } return UNHANDLED; }
static void netlink_recvmsg(int sockfd, struct msghdr * msg, int flags, ssize_t ret) { struct cmsghdr *cmsg; struct sockaddr_nl *sender; if (fd_map_get(&wrapped_netlink_sockets, sockfd, NULL) && ret > 0) { DBG(DBG_NETLINK, "testbed wrapped recvmsg: netlink socket fd %i, got %zi bytes\n", sockfd, ret); /* fake sender to be netlink */ sender = (struct sockaddr_nl *)msg->msg_name; sender->nl_family = AF_NETLINK; sender->nl_pid = 0; sender->nl_groups = 2; /* UDEV_MONITOR_UDEV */ msg->msg_namelen = sizeof(sender); /* fake sender credentials to be uid 0 */ cmsg = CMSG_FIRSTHDR(msg); if (cmsg != NULL) { struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg); cred->uid = 0; } } }
static void script_record_op(char op, int fd, const void *buf, ssize_t size) { struct script_record_info *srinfo; unsigned long delta; libc_func(fwrite, size_t, const void *, size_t, size_t, FILE *); static char header[100]; const unsigned char *cur; int i; if (!fd_map_get(&script_recorded_fds, fd, (const void **)&srinfo)) return; if (size <= 0) return; DBG(DBG_SCRIPT, "script_record_op %c: got %zi bytes on fd %i (format %i)\n", op, size, fd, srinfo->fmt); switch (srinfo->fmt) { case FMT_DEFAULT: delta = update_msec(&srinfo->time); DBG(DBG_SCRIPT, " %lu ms since last operation %c\n", delta, srinfo->op); /* for negligible time deltas, append to the previous stanza, otherwise * create a new record */ if (delta >= 10 || srinfo->op != op) { if (srinfo->op != 0) putc('\n', srinfo->log); snprintf(header, sizeof(header), "%c %lu ", op, delta); assert(_fwrite(header, strlen(header), 1, srinfo->log) == 1); } /* escape ASCII control chars */ for (i = 0, cur = buf; i < size; ++i, ++cur) { if (*cur < 32) { putc('^', srinfo->log); putc(*cur + 64, srinfo->log); continue; } if (*cur == '^') { /* we cannot encode ^ as ^^, as we need that for 0x1E already; so * take the next free code which is 0x60 */ putc('^', srinfo->log); putc('`', srinfo->log); continue; } putc(*cur, srinfo->log); } break; case FMT_EVEMU: if (op != 'r') { fprintf(stderr, "libumockdev-preload: evemu format only supports reads from the device\n"); abort(); } if (size % sizeof(struct input_event) != 0) { fprintf(stderr, "libumockdev-preload: evemu format only supports reading input_event structs\n"); abort(); } const struct input_event *e = buf; while (size > 0) { fprintf(srinfo->log, "E: %li.%06li %04"PRIX16" %04"PRIX16 " %"PRIi32"\n", (long) e->time.tv_sec, (long) e->time.tv_usec, e->type, e->code, e->value); size -= sizeof(struct input_event); e++; } break; default: fprintf(stderr, "libumockdev-preload script_record_op(): unsupported format %i\n", srinfo->fmt); abort(); } fflush(srinfo->log); srinfo->op = op; }
static void script_start_record(int fd, const char *logname, const char *recording_path, enum script_record_format fmt) { FILE *log; struct script_record_info *srinfo; if (fd_map_get(&script_recorded_fds, fd, NULL)) { fprintf(stderr, "script_start_record: internal error: fd %i is already being recorded\n", fd); abort(); } log = fopen(logname, "a+"); if (log == NULL) { perror("umockdev: failed to open script record file"); exit(1); } /* if we have a previous record... */ fseek(log, 0, SEEK_END); if (ftell(log) > 0) { DBG(DBG_SCRIPT, "script_start_record: Appending to existing record of format %i for path %s\n", fmt, recording_path); /* ...and we're going to record the device name... */ if (recording_path) { /* ... ensure we're recording the same device... */ char *existing_device_path; char line[1000]; libc_func(fgets, char *, char *, int, FILE *); fseek(log, 0, SEEK_SET); while (_fgets(line, sizeof(line), log)) { switch (fmt) { case FMT_DEFAULT: /* Start by skipping any leading comments */ if (line[0] == '#') continue; if (sscanf(line, "d 0 %ms\n", &existing_device_path) == 1) { DBG(DBG_SCRIPT, "script_start_record: recording %s, existing device spec in record %s\n", recording_path, existing_device_path); /* We have an existing "d /dev/something" directive, check it matches */ if (strcmp(recording_path, existing_device_path) != 0) { fprintf(stderr, "umockdev: attempt to record two different devices to the same script recording\n"); exit(1); } free(existing_device_path); } // device specification must be on the first non-comment line break; case FMT_EVEMU: if (strncmp(line, "E: ", 3) == 0) break; if (sscanf(line, "# device %ms\n", &existing_device_path) == 1) { DBG(DBG_SCRIPT, "script_start_record evemu format: recording %s, existing device spec in record %s\n", recording_path, existing_device_path); /* We have an existing "/dev/something" directive, check it matches */ if (strcmp(recording_path, existing_device_path) != 0) { fprintf(stderr, "umockdev: attempt to record two different devices to the same evemu recording\n"); exit(1); } free(existing_device_path); } break; default: fprintf(stderr, "umockdev: unknown script format %i\n", fmt); abort(); } } fseek(log, 0, SEEK_END); } /* ...finally, make sure that we start a new line */ putc('\n', log); } else if (recording_path) { /* this is a new record, start by recording the device path */ DBG(DBG_SCRIPT, "script_start_record: Starting new record of format %i\n", fmt); switch (fmt) { case FMT_DEFAULT: fprintf(log, "d 0 %s\n", recording_path); break; case FMT_EVEMU: fprintf(log, "# EVEMU 1.2\n# device %s\n", recording_path); break; default: fprintf(stderr, "umockdev: unknown script format %i\n", fmt); abort(); } } srinfo = malloc(sizeof(struct script_record_info)); srinfo->log = log; assert(clock_gettime(CLOCK_MONOTONIC, &srinfo->time) == 0); srinfo->op = 0; srinfo->fmt = fmt; fd_map_add(&script_recorded_fds, fd, srinfo); }