-
-
Save yuhong-zhong/d7d9077659246891cfc52f0d28e63298 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Makefile b/Makefile | |
index 24a4c1b97bb0..a4c1d842c2e8 100644 | |
--- a/Makefile | |
+++ b/Makefile | |
@@ -2,7 +2,7 @@ | |
VERSION = 5 | |
PATCHLEVEL = 8 | |
SUBLEVEL = 0 | |
-EXTRAVERSION = | |
+EXTRAVERSION =-oliver | |
NAME = Kleptomaniac Octopus | |
# *DOCUMENTATION* | |
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl | |
index 78847b32e137..b9fafce3d1cc 100644 | |
--- a/arch/x86/entry/syscalls/syscall_64.tbl | |
+++ b/arch/x86/entry/syscalls/syscall_64.tbl | |
@@ -360,6 +360,9 @@ | |
437 common openat2 sys_openat2 | |
438 common pidfd_getfd sys_pidfd_getfd | |
439 common faccessat2 sys_faccessat2 | |
+440 common imposter sys_imposter | |
+441 common init_imposter sys_init_imposter | |
+442 common get_imposter sys_get_imposter | |
# | |
# x32-specific system call numbers start at 512 to avoid cache impact | |
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c | |
index 4ee2330c603e..35c5dc6af7b0 100644 | |
--- a/drivers/nvme/host/core.c | |
+++ b/drivers/nvme/host/core.c | |
@@ -21,6 +21,7 @@ | |
#include <linux/nvme_ioctl.h> | |
#include <linux/pm_qos.h> | |
#include <asm/unaligned.h> | |
+#include <linux/hrtimer.h> | |
#include "nvme.h" | |
#include "fabrics.h" | |
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h | |
index 09ffc3246f60..63b143e19ca5 100644 | |
--- a/drivers/nvme/host/nvme.h | |
+++ b/drivers/nvme/host/nvme.h | |
@@ -490,6 +490,12 @@ static inline void nvme_end_request(struct request *req, __le16 status, | |
rq->result = result; | |
/* inject error when permitted by fault injection framework */ | |
nvme_should_fail(req); | |
+ | |
+ if (req->bio && req->bio->_imposter_level > 0 && _imposter_nvme_driver) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_nvme_driver_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_nvme_driver[_index], ktime_sub(ktime_get(), req->bio->_imposter_nvme_driver_start)); | |
+ } | |
+ | |
blk_mq_complete_request(req); | |
} | |
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c | |
index d4b1ff747123..4bee8dd69997 100644 | |
--- a/drivers/nvme/host/pci.c | |
+++ b/drivers/nvme/host/pci.c | |
@@ -24,6 +24,7 @@ | |
#include <linux/io-64-nonatomic-lo-hi.h> | |
#include <linux/sed-opal.h> | |
#include <linux/pci-p2pdma.h> | |
+#include <linux/hrtimer.h> | |
#include "trace.h" | |
#include "nvme.h" | |
@@ -467,8 +468,11 @@ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq) | |
* @write_sq: whether to write to the SQ doorbell | |
*/ | |
static void nvme_submit_cmd(struct nvme_queue *nvmeq, struct nvme_command *cmd, | |
- bool write_sq) | |
+ bool write_sq, struct request *req) | |
{ | |
+ if (req && req->bio && req->bio->_imposter_level > 0) { | |
+ req->bio->_imposter_device_start = ktime_get(); | |
+ } | |
spin_lock(&nvmeq->sq_lock); | |
memcpy(nvmeq->sq_cmds + (nvmeq->sq_tail << nvmeq->sqes), | |
cmd, sizeof(*cmd)); | |
@@ -863,6 +867,10 @@ static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx, | |
struct nvme_command cmnd; | |
blk_status_t ret; | |
+ if (req->bio && req->bio->_imposter_level > 0) { | |
+ req->bio->_imposter_nvme_driver_start = ktime_get(); | |
+ } | |
+ | |
iod->aborted = 0; | |
iod->npages = -1; | |
iod->nents = 0; | |
@@ -891,7 +899,7 @@ static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx, | |
} | |
blk_mq_start_request(req); | |
- nvme_submit_cmd(nvmeq, &cmnd, bd->last); | |
+ nvme_submit_cmd(nvmeq, &cmnd, bd->last, req); | |
return BLK_STS_OK; | |
out_unmap_data: | |
nvme_unmap_data(dev, req); | |
@@ -962,6 +970,12 @@ static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, u16 idx) | |
} | |
req = blk_mq_tag_to_rq(nvme_queue_tagset(nvmeq), cqe->command_id); | |
+ | |
+ if (req->bio && req->bio->_imposter_level > 0 && _imposter_device) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_device_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_device[_index], ktime_sub(ktime_get(), req->bio->_imposter_device_start)); | |
+ } | |
+ | |
trace_nvme_sq(req, cqe->sq_head, nvmeq->sq_tail); | |
nvme_end_request(req, cqe->status, cqe->result); | |
} | |
@@ -1062,7 +1076,7 @@ static void nvme_pci_submit_async_event(struct nvme_ctrl *ctrl) | |
memset(&c, 0, sizeof(c)); | |
c.common.opcode = nvme_admin_async_event; | |
c.common.command_id = NVME_AQ_BLK_MQ_DEPTH; | |
- nvme_submit_cmd(nvmeq, &c, true); | |
+ nvme_submit_cmd(nvmeq, &c, true, NULL); | |
} | |
static int adapter_delete_queue(struct nvme_dev *dev, u8 opcode, u16 id) | |
diff --git a/fs/block_dev.c b/fs/block_dev.c | |
index 0ae656e022fd..14e5d0482c7e 100644 | |
--- a/fs/block_dev.c | |
+++ b/fs/block_dev.c | |
@@ -193,6 +193,11 @@ static void blkdev_bio_end_io_simple(struct bio *bio) | |
{ | |
struct task_struct *waiter = bio->bi_private; | |
+ if (bio && bio->_imposter_level > 0 && _imposter_bio) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_bio_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_bio[_index], ktime_sub(ktime_get(), bio->_imposter_bio_start)); | |
+ } | |
+ | |
WRITE_ONCE(bio->bi_private, NULL); | |
blk_wake_io_task(waiter); | |
} | |
@@ -230,6 +235,8 @@ __blkdev_direct_IO_simple(struct kiocb *iocb, struct iov_iter *iter, | |
bio.bi_private = current; | |
bio.bi_end_io = blkdev_bio_end_io_simple; | |
bio.bi_ioprio = iocb->ki_ioprio; | |
+ bio._imposter_level = file->_imposter_level; | |
+ bio._imposter_bio_start = ktime_get(); | |
ret = bio_iov_iter_get_pages(&bio, iter); | |
if (unlikely(ret)) | |
@@ -299,6 +306,11 @@ static void blkdev_bio_end_io(struct bio *bio) | |
struct blkdev_dio *dio = bio->bi_private; | |
bool should_dirty = dio->should_dirty; | |
+ if (bio && bio->_imposter_level > 0 && _imposter_bio) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_bio_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_bio[_index], ktime_sub(ktime_get(), bio->_imposter_bio_start)); | |
+ } | |
+ | |
if (bio->bi_status && !dio->bio.bi_status) | |
dio->bio.bi_status = bio->bi_status; | |
@@ -381,6 +393,8 @@ __blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter, int nr_pages) | |
bio->bi_private = dio; | |
bio->bi_end_io = blkdev_bio_end_io; | |
bio->bi_ioprio = iocb->ki_ioprio; | |
+ bio->_imposter_level = file->_imposter_level; | |
+ bio->_imposter_bio_start = ktime_get(); | |
ret = bio_iov_iter_get_pages(bio, iter); | |
if (unlikely(ret)) { | |
diff --git a/fs/ioctl.c b/fs/ioctl.c | |
index d69786d1dd91..d3bbcb03d806 100644 | |
--- a/fs/ioctl.c | |
+++ b/fs/ioctl.c | |
@@ -762,6 +762,148 @@ SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) | |
return ksys_ioctl(fd, cmd, arg); | |
} | |
+long *_imposter_device; | |
+EXPORT_SYMBOL(_imposter_device); | |
+long *_imposter_nvme_driver; | |
+EXPORT_SYMBOL(_imposter_nvme_driver); | |
+long *_imposter_bio; | |
+EXPORT_SYMBOL(_imposter_bio); | |
+long *_imposter_fs; | |
+EXPORT_SYMBOL(_imposter_fs); | |
+long *_imposter_syscall; | |
+EXPORT_SYMBOL(_imposter_syscall); | |
+ | |
+atomic_long_t _imposter_device_index; | |
+EXPORT_SYMBOL(_imposter_device_index); | |
+atomic_long_t _imposter_nvme_driver_index; | |
+EXPORT_SYMBOL(_imposter_nvme_driver_index); | |
+atomic_long_t _imposter_bio_index; | |
+EXPORT_SYMBOL(_imposter_bio_index); | |
+atomic_long_t _imposter_fs_index; | |
+EXPORT_SYMBOL(_imposter_fs_index); | |
+atomic_long_t _imposter_syscall_index; | |
+EXPORT_SYMBOL(_imposter_syscall_index); | |
+ | |
+SYSCALL_DEFINE2(imposter, int, fd, int, level) | |
+{ | |
+ struct fd f = fdget_pos(fd); | |
+ long ret = -EBADF; | |
+ | |
+ if (f.file) { | |
+ f.file->_imposter_level = level; | |
+ fdput_pos(f); | |
+ ret = 0; | |
+ } else { | |
+ printk("imposter: bad file descriptor\n"); | |
+ } | |
+ | |
+ return ret; | |
+} | |
+ | |
+SYSCALL_DEFINE0(init_imposter) | |
+{ | |
+ long i; | |
+ | |
+ _imposter_device = vmalloc(_IMPOSTER_ARR_SIZE * sizeof(long)); | |
+ if (!_imposter_device) { | |
+ printk("cannot allocate buffer for _imposter_device\n"); | |
+ return -ENOMEM; | |
+ } | |
+ _imposter_nvme_driver = vmalloc(_IMPOSTER_ARR_SIZE * sizeof(long)); | |
+ if (!_imposter_nvme_driver) { | |
+ printk("cannot allocate buffer for _imposter_nvme_driver\n"); | |
+ return -ENOMEM; | |
+ } | |
+ _imposter_bio = vmalloc(_IMPOSTER_ARR_SIZE * sizeof(long)); | |
+ if (!_imposter_bio) { | |
+ printk("cannot allocate buffer for _imposter_bio\n"); | |
+ return -ENOMEM; | |
+ } | |
+ _imposter_fs = vmalloc(_IMPOSTER_ARR_SIZE * sizeof(long)); | |
+ if (!_imposter_fs) { | |
+ printk("cannot allocate buffer for _imposter_fs\n"); | |
+ return -ENOMEM; | |
+ } | |
+ _imposter_syscall = vmalloc(_IMPOSTER_ARR_SIZE * sizeof(long)); | |
+ if (!_imposter_syscall) { | |
+ printk("cannot allocate buffer for _imposter_syscall\n"); | |
+ return -ENOMEM; | |
+ } | |
+ | |
+ for (i = 0; i < _IMPOSTER_ARR_SIZE; ++i) { | |
+ _imposter_device[i] = -1; | |
+ _imposter_nvme_driver[i] = -1; | |
+ _imposter_bio[i] = -1; | |
+ _imposter_fs[i] = -1; | |
+ _imposter_syscall[i] = -1; | |
+ } | |
+ | |
+ atomic_long_set(&_imposter_device_index, 0); | |
+ atomic_long_set(&_imposter_nvme_driver_index, 0); | |
+ atomic_long_set(&_imposter_bio_index, 0); | |
+ atomic_long_set(&_imposter_fs_index, 0); | |
+ atomic_long_set(&_imposter_syscall_index, 0); | |
+ | |
+ return 0; | |
+} | |
+ | |
+SYSCALL_DEFINE2(get_imposter, int, type, long, index) | |
+{ | |
+ if (index < 0 || index >= _IMPOSTER_ARR_SIZE) { | |
+ printk("get_imposter: invalid index\n"); | |
+ return -1; | |
+ } | |
+ | |
+ if (type < 0 || type >= 5) { | |
+ printk("get_imposter: invalid type\n"); | |
+ return -1; | |
+ } | |
+ | |
+ long value; | |
+ switch (type) { | |
+ case 0: | |
+ if (!_imposter_device) { | |
+ value = -1; | |
+ } else { | |
+ value = READ_ONCE(_imposter_device[index]); | |
+ } | |
+ break; | |
+ case 1: | |
+ if (!_imposter_nvme_driver) { | |
+ value = -1; | |
+ } else { | |
+ value = READ_ONCE(_imposter_nvme_driver[index]); | |
+ } | |
+ break; | |
+ case 2: | |
+ if (!_imposter_bio) { | |
+ value = -1; | |
+ } else { | |
+ value = READ_ONCE(_imposter_bio[index]); | |
+ } | |
+ break; | |
+ case 3: | |
+ if (!_imposter_fs) { | |
+ value = -1; | |
+ } else { | |
+ value = READ_ONCE(_imposter_fs[index]); | |
+ } | |
+ break; | |
+ case 4: | |
+ if (!_imposter_syscall) { | |
+ value = -1; | |
+ } else { | |
+ value = READ_ONCE(_imposter_syscall[index]); | |
+ } | |
+ break; | |
+ default: | |
+ printk("get_imposter: unrecognized type\n"); | |
+ value = -1; | |
+ } | |
+ | |
+ return value; | |
+} | |
+ | |
#ifdef CONFIG_COMPAT | |
/** | |
* compat_ptr_ioctl - generic implementation of .compat_ioctl file operation | |
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c | |
index ec7b78e6feca..c9b3f656024b 100644 | |
--- a/fs/iomap/direct-io.c | |
+++ b/fs/iomap/direct-io.c | |
@@ -67,6 +67,11 @@ static void iomap_dio_submit_bio(struct iomap_dio *dio, struct iomap *iomap, | |
bio_set_polled(bio, dio->iocb); | |
dio->submit.last_queue = bdev_get_queue(iomap->bdev); | |
+ | |
+ if (bio && bio->_imposter_level > 0) { | |
+ bio->_imposter_bio_start = ktime_get(); | |
+ } | |
+ | |
if (dio->dops && dio->dops->submit_io) | |
dio->submit.cookie = dio->dops->submit_io( | |
file_inode(dio->iocb->ki_filp), | |
@@ -153,6 +158,11 @@ static void iomap_dio_bio_end_io(struct bio *bio) | |
struct iomap_dio *dio = bio->bi_private; | |
bool should_dirty = (dio->flags & IOMAP_DIO_DIRTY); | |
+ if (bio && bio->_imposter_level > 0 && _imposter_bio) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_bio_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_bio[_index], ktime_sub(ktime_get(), bio->_imposter_bio_start)); | |
+ } | |
+ | |
if (bio->bi_status) | |
iomap_dio_set_error(dio, blk_status_to_errno(bio->bi_status)); | |
@@ -276,6 +286,7 @@ iomap_dio_bio_actor(struct inode *inode, loff_t pos, loff_t length, | |
bio->bi_ioprio = dio->iocb->ki_ioprio; | |
bio->bi_private = dio; | |
bio->bi_end_io = iomap_dio_bio_end_io; | |
+ bio->_imposter_level = dio->iocb->ki_filp->_imposter_level; | |
ret = bio_iov_iter_get_pages(bio, dio->submit.iter); | |
if (unlikely(ret)) { | |
diff --git a/fs/read_write.c b/fs/read_write.c | |
index 4fb797822567..1f86de544fde 100644 | |
--- a/fs/read_write.c | |
+++ b/fs/read_write.c | |
@@ -21,6 +21,7 @@ | |
#include <linux/mount.h> | |
#include <linux/fs.h> | |
#include "internal.h" | |
+#include <linux/hrtimer.h> | |
#include <linux/uaccess.h> | |
#include <asm/unistd.h> | |
@@ -614,7 +615,57 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count) | |
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) | |
{ | |
- return ksys_read(fd, buf, count); | |
+ struct fd f; | |
+ int _imposter_level; | |
+ loff_t pos, *ppos; | |
+ | |
+ /* check imposter info */ | |
+ f = fdget_pos(fd); | |
+ if (!f.file) { | |
+ return -EBADF; | |
+ } | |
+ _imposter_level = f.file->_imposter_level; | |
+ ppos = file_ppos(f.file); | |
+ if (ppos) { | |
+ pos = *ppos; | |
+ } | |
+ fdput_pos(f); | |
+ | |
+ if (_imposter_level == 0) { | |
+ /* normal read */ | |
+ return ksys_read(fd, buf, count); | |
+ } else { | |
+ /* imposter read */ | |
+ ktime_t _imposter_syscall_start = ktime_get(); | |
+ | |
+ if (!ppos) { | |
+ printk("imposter read: invalid offset\n"); | |
+ return -EBADF; | |
+ } | |
+ long index = pos >> 12; | |
+ int i; | |
+ for (i = 0; i < _imposter_level; ++i) { | |
+ if (i > 0) { | |
+ off_t lseek_ret = ksys_lseek(fd, index << 12, SEEK_SET); | |
+ if (lseek_ret != index << 12) { | |
+ printk("imposter read: ksys_lseek failed\n"); | |
+ return -EBADF; | |
+ } | |
+ } | |
+ ssize_t read_ret = ksys_read(fd, buf, count); | |
+ if (read_ret != count) { | |
+ printk("imposter read: ksys_read failed\n"); | |
+ return -EBADF; | |
+ } | |
+ index = (index * 1103515245 + 12345) % (1 << 23); | |
+ } | |
+ | |
+ if (_imposter_syscall) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_syscall_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_syscall[_index], ktime_sub(ktime_get(), _imposter_syscall_start)); | |
+ } | |
+ return count; | |
+ } | |
} | |
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count) | |
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h | |
index ccb895f911b1..d129cb2301aa 100644 | |
--- a/include/linux/blk_types.h | |
+++ b/include/linux/blk_types.h | |
@@ -211,6 +211,11 @@ struct bio { | |
struct bio_set *bi_pool; | |
+ int _imposter_level; | |
+ ktime_t _imposter_device_start; | |
+ ktime_t _imposter_nvme_driver_start; | |
+ ktime_t _imposter_bio_start; | |
+ | |
/* | |
* We can inline a number of vecs at the end of the bio, to avoid | |
* double allocations for a small number of bio_vecs. This member | |
diff --git a/include/linux/fs.h b/include/linux/fs.h | |
index f5abba86107d..ee3f29d055a5 100644 | |
--- a/include/linux/fs.h | |
+++ b/include/linux/fs.h | |
@@ -39,10 +39,25 @@ | |
#include <linux/fs_types.h> | |
#include <linux/build_bug.h> | |
#include <linux/stddef.h> | |
+#include <linux/hrtimer.h> | |
#include <asm/byteorder.h> | |
#include <uapi/linux/fs.h> | |
+#ifndef _IMPOSTER_ARR_SIZE | |
+#define _IMPOSTER_ARR_SIZE 3750000 | |
+#endif | |
+extern long *_imposter_device; | |
+extern long *_imposter_nvme_driver; | |
+extern long *_imposter_bio; | |
+extern long *_imposter_fs; | |
+extern long *_imposter_syscall; | |
+extern atomic_long_t _imposter_device_index; | |
+extern atomic_long_t _imposter_nvme_driver_index; | |
+extern atomic_long_t _imposter_bio_index; | |
+extern atomic_long_t _imposter_fs_index; | |
+extern atomic_long_t _imposter_syscall_index; | |
+ | |
struct backing_dev_info; | |
struct bdi_writeback; | |
struct bio; | |
@@ -950,6 +965,8 @@ struct file { | |
struct inode *f_inode; /* cached value */ | |
const struct file_operations *f_op; | |
+ int _imposter_level; | |
+ | |
/* | |
* Protects f_ep_links, f_flags. | |
* Must not be taken from IRQ context. | |
@@ -1899,7 +1916,17 @@ struct inode_operations { | |
static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio, | |
struct iov_iter *iter) | |
{ | |
- return file->f_op->read_iter(kio, iter); | |
+ ktime_t _imposter_fs_start; | |
+ ssize_t ret; | |
+ if (file && file->_imposter_level > 0 && _imposter_fs) { | |
+ _imposter_fs_start = ktime_get(); | |
+ } | |
+ ret = file->f_op->read_iter(kio, iter); | |
+ if (file && file->_imposter_level > 0 && _imposter_fs) { | |
+ long _index = atomic_long_fetch_inc(&_imposter_fs_index) % _IMPOSTER_ARR_SIZE; | |
+ WRITE_ONCE(_imposter_fs[_index], ktime_sub(ktime_get(), _imposter_fs_start)); | |
+ } | |
+ return ret; | |
} | |
static inline ssize_t call_write_iter(struct file *file, struct kiocb *kio, | |
diff --git a/include/linux/slab.h b/include/linux/slab.h | |
index 6d454886bcaf..96118ff7dc98 100644 | |
--- a/include/linux/slab.h | |
+++ b/include/linux/slab.h | |
@@ -135,6 +135,20 @@ | |
#include <linux/kasan.h> | |
+#ifndef _IMPOSTER_ARR_SIZE | |
+#define _IMPOSTER_ARR_SIZE 3750000 | |
+#endif | |
+extern long *_imposter_device; | |
+extern long *_imposter_nvme_driver; | |
+extern long *_imposter_bio; | |
+extern long *_imposter_fs; | |
+extern long *_imposter_syscall; | |
+extern atomic_long_t _imposter_device_index; | |
+extern atomic_long_t _imposter_nvme_driver_index; | |
+extern atomic_long_t _imposter_bio_index; | |
+extern atomic_long_t _imposter_fs_index; | |
+extern atomic_long_t _imposter_syscall_index; | |
+ | |
struct mem_cgroup; | |
/* | |
* struct kmem_cache related prototypes | |
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h | |
index b951a87da987..a4136cbb5aa0 100644 | |
--- a/include/linux/syscalls.h | |
+++ b/include/linux/syscalls.h | |
@@ -1424,4 +1424,8 @@ long compat_ksys_semtimedop(int semid, struct sembuf __user *tsems, | |
unsigned int nsops, | |
const struct old_timespec32 __user *timeout); | |
+asmlinkage long sys_imposter(int fd, int level); | |
+asmlinkage long sys_init_imposter(void); | |
+asmlinkage long sys_get_imposter(int type, long index); | |
+ | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment