| #include "kvm/util.h" |
| #include "kvm/virtio-9p.h" |
| |
| #include <endian.h> |
| #include <stdint.h> |
| |
| #include <linux/compiler.h> |
| #include <linux/9p.h> |
| |
| static void virtio_p9_pdu_read(struct p9_pdu *pdu, void *data, size_t size) |
| { |
| size_t len; |
| int i, copied = 0; |
| u16 iov_cnt = pdu->out_iov_cnt; |
| size_t offset = pdu->read_offset; |
| struct iovec *iov = pdu->out_iov; |
| |
| for (i = 0; i < iov_cnt && size; i++) { |
| if (offset >= iov[i].iov_len) { |
| offset -= iov[i].iov_len; |
| continue; |
| } else { |
| len = MIN(iov[i].iov_len - offset, size); |
| memcpy(data, iov[i].iov_base + offset, len); |
| size -= len; |
| data += len; |
| offset = 0; |
| copied += len; |
| } |
| } |
| pdu->read_offset += copied; |
| } |
| |
| static void virtio_p9_pdu_write(struct p9_pdu *pdu, |
| const void *data, size_t size) |
| { |
| size_t len; |
| int i, copied = 0; |
| u16 iov_cnt = pdu->in_iov_cnt; |
| size_t offset = pdu->write_offset; |
| struct iovec *iov = pdu->in_iov; |
| |
| for (i = 0; i < iov_cnt && size; i++) { |
| if (offset >= iov[i].iov_len) { |
| offset -= iov[i].iov_len; |
| continue; |
| } else { |
| len = MIN(iov[i].iov_len - offset, size); |
| memcpy(iov[i].iov_base + offset, data, len); |
| size -= len; |
| data += len; |
| offset = 0; |
| copied += len; |
| } |
| } |
| pdu->write_offset += copied; |
| } |
| |
| static void virtio_p9_wstat_free(struct p9_wstat *stbuf) |
| { |
| free(stbuf->name); |
| free(stbuf->uid); |
| free(stbuf->gid); |
| free(stbuf->muid); |
| } |
| |
| static int virtio_p9_decode(struct p9_pdu *pdu, const char *fmt, va_list ap) |
| { |
| int retval = 0; |
| const char *ptr; |
| |
| for (ptr = fmt; *ptr; ptr++) { |
| switch (*ptr) { |
| case 'b': |
| { |
| int8_t *val = va_arg(ap, int8_t *); |
| virtio_p9_pdu_read(pdu, val, sizeof(*val)); |
| } |
| break; |
| case 'w': |
| { |
| int16_t le_val; |
| int16_t *val = va_arg(ap, int16_t *); |
| virtio_p9_pdu_read(pdu, &le_val, sizeof(le_val)); |
| *val = le16toh(le_val); |
| } |
| break; |
| case 'd': |
| { |
| int32_t le_val; |
| int32_t *val = va_arg(ap, int32_t *); |
| virtio_p9_pdu_read(pdu, &le_val, sizeof(le_val)); |
| *val = le32toh(le_val); |
| } |
| break; |
| case 'q': |
| { |
| int64_t le_val; |
| int64_t *val = va_arg(ap, int64_t *); |
| virtio_p9_pdu_read(pdu, &le_val, sizeof(le_val)); |
| *val = le64toh(le_val); |
| } |
| break; |
| case 's': |
| { |
| int16_t len; |
| char **str = va_arg(ap, char **); |
| |
| virtio_p9_pdu_readf(pdu, "w", &len); |
| *str = malloc(len + 1); |
| if (*str == NULL) { |
| retval = ENOMEM; |
| break; |
| } |
| virtio_p9_pdu_read(pdu, *str, len); |
| (*str)[len] = 0; |
| } |
| break; |
| case 'Q': |
| { |
| struct p9_qid *qid = va_arg(ap, struct p9_qid *); |
| retval = virtio_p9_pdu_readf(pdu, "bdq", |
| &qid->type, &qid->version, |
| &qid->path); |
| } |
| break; |
| case 'S': |
| { |
| struct p9_wstat *stbuf = va_arg(ap, struct p9_wstat *); |
| memset(stbuf, 0, sizeof(struct p9_wstat)); |
| stbuf->n_uid = KUIDT_INIT(-1); |
| stbuf->n_gid = KGIDT_INIT(-1); |
| stbuf->n_muid = KUIDT_INIT(-1); |
| retval = virtio_p9_pdu_readf(pdu, "wwdQdddqssss", |
| &stbuf->size, &stbuf->type, |
| &stbuf->dev, &stbuf->qid, |
| &stbuf->mode, &stbuf->atime, |
| &stbuf->mtime, &stbuf->length, |
| &stbuf->name, &stbuf->uid, |
| &stbuf->gid, &stbuf->muid); |
| if (retval) |
| virtio_p9_wstat_free(stbuf); |
| } |
| break; |
| case 'I': |
| { |
| struct p9_iattr_dotl *p9attr = va_arg(ap, |
| struct p9_iattr_dotl *); |
| |
| retval = virtio_p9_pdu_readf(pdu, "ddddqqqqq", |
| &p9attr->valid, |
| &p9attr->mode, |
| &p9attr->uid, |
| &p9attr->gid, |
| &p9attr->size, |
| &p9attr->atime_sec, |
| &p9attr->atime_nsec, |
| &p9attr->mtime_sec, |
| &p9attr->mtime_nsec); |
| } |
| break; |
| default: |
| retval = EINVAL; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| static int virtio_p9_pdu_encode(struct p9_pdu *pdu, const char *fmt, va_list ap) |
| { |
| int retval = 0; |
| const char *ptr; |
| |
| for (ptr = fmt; *ptr; ptr++) { |
| switch (*ptr) { |
| case 'b': |
| { |
| int8_t val = va_arg(ap, int); |
| virtio_p9_pdu_write(pdu, &val, sizeof(val)); |
| } |
| break; |
| case 'w': |
| { |
| int16_t val = htole16(va_arg(ap, int)); |
| virtio_p9_pdu_write(pdu, &val, sizeof(val)); |
| } |
| break; |
| case 'd': |
| { |
| int32_t val = htole32(va_arg(ap, int32_t)); |
| virtio_p9_pdu_write(pdu, &val, sizeof(val)); |
| } |
| break; |
| case 'q': |
| { |
| int64_t val = htole64(va_arg(ap, int64_t)); |
| virtio_p9_pdu_write(pdu, &val, sizeof(val)); |
| } |
| break; |
| case 's': |
| { |
| uint16_t len = 0; |
| const char *s = va_arg(ap, char *); |
| if (s) |
| len = MIN(strlen(s), USHRT_MAX); |
| virtio_p9_pdu_writef(pdu, "w", len); |
| virtio_p9_pdu_write(pdu, s, len); |
| } |
| break; |
| case 'Q': |
| { |
| struct p9_qid *qid = va_arg(ap, struct p9_qid *); |
| retval = virtio_p9_pdu_writef(pdu, "bdq", |
| qid->type, qid->version, |
| qid->path); |
| } |
| break; |
| case 'S': |
| { |
| struct p9_wstat *stbuf = va_arg(ap, struct p9_wstat *); |
| retval = virtio_p9_pdu_writef(pdu, "wwdQdddqssss", |
| stbuf->size, stbuf->type, |
| stbuf->dev, &stbuf->qid, |
| stbuf->mode, stbuf->atime, |
| stbuf->mtime, stbuf->length, |
| stbuf->name, stbuf->uid, |
| stbuf->gid, stbuf->muid); |
| } |
| break; |
| case 'A': |
| { |
| struct p9_stat_dotl *stbuf = va_arg(ap, |
| struct p9_stat_dotl *); |
| retval = virtio_p9_pdu_writef(pdu, |
| "qQdddqqqqqqqqqqqqqqq", |
| stbuf->st_result_mask, |
| &stbuf->qid, |
| stbuf->st_mode, |
| stbuf->st_uid, |
| stbuf->st_gid, |
| stbuf->st_nlink, |
| stbuf->st_rdev, |
| stbuf->st_size, |
| stbuf->st_blksize, |
| stbuf->st_blocks, |
| stbuf->st_atime_sec, |
| stbuf->st_atime_nsec, |
| stbuf->st_mtime_sec, |
| stbuf->st_mtime_nsec, |
| stbuf->st_ctime_sec, |
| stbuf->st_ctime_nsec, |
| stbuf->st_btime_sec, |
| stbuf->st_btime_nsec, |
| stbuf->st_gen, |
| stbuf->st_data_version); |
| } |
| break; |
| default: |
| retval = EINVAL; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| int virtio_p9_pdu_readf(struct p9_pdu *pdu, const char *fmt, ...) |
| { |
| int ret; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| ret = virtio_p9_decode(pdu, fmt, ap); |
| va_end(ap); |
| |
| return ret; |
| } |
| |
| int virtio_p9_pdu_writef(struct p9_pdu *pdu, const char *fmt, ...) |
| { |
| int ret; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| ret = virtio_p9_pdu_encode(pdu, fmt, ap); |
| va_end(ap); |
| |
| return ret; |
| } |