162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2017 Omnibond Systems, L.L.C. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include "protocol.h" 762306a36Sopenharmony_ci#include "orangefs-kernel.h" 862306a36Sopenharmony_ci#include "orangefs-bufmap.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_cistruct orangefs_dir_part { 1162306a36Sopenharmony_ci struct orangefs_dir_part *next; 1262306a36Sopenharmony_ci size_t len; 1362306a36Sopenharmony_ci}; 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistruct orangefs_dir { 1662306a36Sopenharmony_ci __u64 token; 1762306a36Sopenharmony_ci struct orangefs_dir_part *part; 1862306a36Sopenharmony_ci loff_t end; 1962306a36Sopenharmony_ci int error; 2062306a36Sopenharmony_ci}; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define PART_SHIFT (24) 2362306a36Sopenharmony_ci#define PART_SIZE (1<<24) 2462306a36Sopenharmony_ci#define PART_MASK (~(PART_SIZE - 1)) 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* 2762306a36Sopenharmony_ci * There can be up to 512 directory entries. Each entry is encoded as 2862306a36Sopenharmony_ci * follows: 2962306a36Sopenharmony_ci * 4 bytes: string size (n) 3062306a36Sopenharmony_ci * n bytes: string 3162306a36Sopenharmony_ci * 1 byte: trailing zero 3262306a36Sopenharmony_ci * padding to 8 bytes 3362306a36Sopenharmony_ci * 16 bytes: khandle 3462306a36Sopenharmony_ci * padding to 8 bytes 3562306a36Sopenharmony_ci * 3662306a36Sopenharmony_ci * The trailer_buf starts with a struct orangefs_readdir_response_s 3762306a36Sopenharmony_ci * which must be skipped to get to the directory data. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * The data which is received from the userspace daemon is termed a 4062306a36Sopenharmony_ci * part and is stored in a linked list in case more than one part is 4162306a36Sopenharmony_ci * needed for a large directory. 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * The position pointer (ctx->pos) encodes the part and offset on which 4462306a36Sopenharmony_ci * to begin reading at. Bits above PART_SHIFT encode the part and bits 4562306a36Sopenharmony_ci * below PART_SHIFT encode the offset. Parts are stored in a linked 4662306a36Sopenharmony_ci * list which grows as data is received from the server. The overhead 4762306a36Sopenharmony_ci * associated with managing the list is presumed to be small compared to 4862306a36Sopenharmony_ci * the overhead of communicating with the server. 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * As data is received from the server, it is placed at the end of the 5162306a36Sopenharmony_ci * part list. Data is parsed from the current position as it is needed. 5262306a36Sopenharmony_ci * When data is determined to be corrupt, it is either because the 5362306a36Sopenharmony_ci * userspace component has sent back corrupt data or because the file 5462306a36Sopenharmony_ci * pointer has been moved to an invalid location. Since the two cannot 5562306a36Sopenharmony_ci * be differentiated, return EIO. 5662306a36Sopenharmony_ci * 5762306a36Sopenharmony_ci * Part zero is synthesized to contains `.' and `..'. Part one is the 5862306a36Sopenharmony_ci * first part of the part list. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int do_readdir(struct orangefs_inode_s *oi, 6262306a36Sopenharmony_ci struct orangefs_dir *od, struct dentry *dentry, 6362306a36Sopenharmony_ci struct orangefs_kernel_op_s *op) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct orangefs_readdir_response_s *resp; 6662306a36Sopenharmony_ci int bufi, r; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* 6962306a36Sopenharmony_ci * Despite the badly named field, readdir does not use shared 7062306a36Sopenharmony_ci * memory. However, there are a limited number of readdir 7162306a36Sopenharmony_ci * slots, which must be allocated here. This flag simply tells 7262306a36Sopenharmony_ci * the op scheduler to return the op here for retry. 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_ci op->uses_shared_memory = 1; 7562306a36Sopenharmony_ci op->upcall.req.readdir.refn = oi->refn; 7662306a36Sopenharmony_ci op->upcall.req.readdir.token = od->token; 7762306a36Sopenharmony_ci op->upcall.req.readdir.max_dirent_count = 7862306a36Sopenharmony_ci ORANGEFS_MAX_DIRENT_COUNT_READDIR; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ciagain: 8162306a36Sopenharmony_ci bufi = orangefs_readdir_index_get(); 8262306a36Sopenharmony_ci if (bufi < 0) { 8362306a36Sopenharmony_ci od->error = bufi; 8462306a36Sopenharmony_ci return bufi; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci op->upcall.req.readdir.buf_index = bufi; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci r = service_operation(op, "orangefs_readdir", 9062306a36Sopenharmony_ci get_interruptible_flag(dentry->d_inode)); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci orangefs_readdir_index_put(bufi); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if (op_state_purged(op)) { 9562306a36Sopenharmony_ci if (r == -EAGAIN) { 9662306a36Sopenharmony_ci vfree(op->downcall.trailer_buf); 9762306a36Sopenharmony_ci goto again; 9862306a36Sopenharmony_ci } else if (r == -EIO) { 9962306a36Sopenharmony_ci vfree(op->downcall.trailer_buf); 10062306a36Sopenharmony_ci od->error = r; 10162306a36Sopenharmony_ci return r; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (r < 0) { 10662306a36Sopenharmony_ci vfree(op->downcall.trailer_buf); 10762306a36Sopenharmony_ci od->error = r; 10862306a36Sopenharmony_ci return r; 10962306a36Sopenharmony_ci } else if (op->downcall.status) { 11062306a36Sopenharmony_ci vfree(op->downcall.trailer_buf); 11162306a36Sopenharmony_ci od->error = op->downcall.status; 11262306a36Sopenharmony_ci return op->downcall.status; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* 11662306a36Sopenharmony_ci * The maximum size is size per entry times the 512 entries plus 11762306a36Sopenharmony_ci * the header. This is well under the limit. 11862306a36Sopenharmony_ci */ 11962306a36Sopenharmony_ci if (op->downcall.trailer_size > PART_SIZE) { 12062306a36Sopenharmony_ci vfree(op->downcall.trailer_buf); 12162306a36Sopenharmony_ci od->error = -EIO; 12262306a36Sopenharmony_ci return -EIO; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci resp = (struct orangefs_readdir_response_s *) 12662306a36Sopenharmony_ci op->downcall.trailer_buf; 12762306a36Sopenharmony_ci od->token = resp->token; 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic int parse_readdir(struct orangefs_dir *od, 13262306a36Sopenharmony_ci struct orangefs_kernel_op_s *op) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct orangefs_dir_part *part, *new; 13562306a36Sopenharmony_ci size_t count; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci count = 1; 13862306a36Sopenharmony_ci part = od->part; 13962306a36Sopenharmony_ci while (part) { 14062306a36Sopenharmony_ci count++; 14162306a36Sopenharmony_ci if (part->next) 14262306a36Sopenharmony_ci part = part->next; 14362306a36Sopenharmony_ci else 14462306a36Sopenharmony_ci break; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci new = (void *)op->downcall.trailer_buf; 14862306a36Sopenharmony_ci new->next = NULL; 14962306a36Sopenharmony_ci new->len = op->downcall.trailer_size - 15062306a36Sopenharmony_ci sizeof(struct orangefs_readdir_response_s); 15162306a36Sopenharmony_ci if (!od->part) 15262306a36Sopenharmony_ci od->part = new; 15362306a36Sopenharmony_ci else 15462306a36Sopenharmony_ci part->next = new; 15562306a36Sopenharmony_ci count++; 15662306a36Sopenharmony_ci od->end = count << PART_SHIFT; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return 0; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int orangefs_dir_more(struct orangefs_inode_s *oi, 16262306a36Sopenharmony_ci struct orangefs_dir *od, struct dentry *dentry) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct orangefs_kernel_op_s *op; 16562306a36Sopenharmony_ci int r; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci op = op_alloc(ORANGEFS_VFS_OP_READDIR); 16862306a36Sopenharmony_ci if (!op) { 16962306a36Sopenharmony_ci od->error = -ENOMEM; 17062306a36Sopenharmony_ci return -ENOMEM; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci r = do_readdir(oi, od, dentry, op); 17362306a36Sopenharmony_ci if (r) { 17462306a36Sopenharmony_ci od->error = r; 17562306a36Sopenharmony_ci goto out; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci r = parse_readdir(od, op); 17862306a36Sopenharmony_ci if (r) { 17962306a36Sopenharmony_ci od->error = r; 18062306a36Sopenharmony_ci goto out; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci od->error = 0; 18462306a36Sopenharmony_ciout: 18562306a36Sopenharmony_ci op_release(op); 18662306a36Sopenharmony_ci return od->error; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int fill_from_part(struct orangefs_dir_part *part, 19062306a36Sopenharmony_ci struct dir_context *ctx) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci const int offset = sizeof(struct orangefs_readdir_response_s); 19362306a36Sopenharmony_ci struct orangefs_khandle *khandle; 19462306a36Sopenharmony_ci __u32 *len, padlen; 19562306a36Sopenharmony_ci loff_t i; 19662306a36Sopenharmony_ci char *s; 19762306a36Sopenharmony_ci i = ctx->pos & ~PART_MASK; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* The file offset from userspace is too large. */ 20062306a36Sopenharmony_ci if (i > part->len) 20162306a36Sopenharmony_ci return 1; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* 20462306a36Sopenharmony_ci * If the seek pointer is positioned just before an entry it 20562306a36Sopenharmony_ci * should find the next entry. 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_ci if (i % 8) 20862306a36Sopenharmony_ci i = i + (8 - i%8)%8; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci while (i < part->len) { 21162306a36Sopenharmony_ci if (part->len < i + sizeof *len) 21262306a36Sopenharmony_ci break; 21362306a36Sopenharmony_ci len = (void *)part + offset + i; 21462306a36Sopenharmony_ci /* 21562306a36Sopenharmony_ci * len is the size of the string itself. padlen is the 21662306a36Sopenharmony_ci * total size of the encoded string. 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci padlen = (sizeof *len + *len + 1) + 21962306a36Sopenharmony_ci (8 - (sizeof *len + *len + 1)%8)%8; 22062306a36Sopenharmony_ci if (part->len < i + padlen + sizeof *khandle) 22162306a36Sopenharmony_ci goto next; 22262306a36Sopenharmony_ci s = (void *)part + offset + i + sizeof *len; 22362306a36Sopenharmony_ci if (s[*len] != 0) 22462306a36Sopenharmony_ci goto next; 22562306a36Sopenharmony_ci khandle = (void *)part + offset + i + padlen; 22662306a36Sopenharmony_ci if (!dir_emit(ctx, s, *len, 22762306a36Sopenharmony_ci orangefs_khandle_to_ino(khandle), 22862306a36Sopenharmony_ci DT_UNKNOWN)) 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci i += padlen + sizeof *khandle; 23162306a36Sopenharmony_ci i = i + (8 - i%8)%8; 23262306a36Sopenharmony_ci BUG_ON(i > part->len); 23362306a36Sopenharmony_ci ctx->pos = (ctx->pos & PART_MASK) | i; 23462306a36Sopenharmony_ci continue; 23562306a36Sopenharmony_cinext: 23662306a36Sopenharmony_ci i += 8; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci return 1; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic int orangefs_dir_fill(struct orangefs_inode_s *oi, 24262306a36Sopenharmony_ci struct orangefs_dir *od, struct dentry *dentry, 24362306a36Sopenharmony_ci struct dir_context *ctx) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct orangefs_dir_part *part; 24662306a36Sopenharmony_ci size_t count; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci count = ((ctx->pos & PART_MASK) >> PART_SHIFT) - 1; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci part = od->part; 25162306a36Sopenharmony_ci while (part->next && count) { 25262306a36Sopenharmony_ci count--; 25362306a36Sopenharmony_ci part = part->next; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci /* This means the userspace file offset is invalid. */ 25662306a36Sopenharmony_ci if (count) { 25762306a36Sopenharmony_ci od->error = -EIO; 25862306a36Sopenharmony_ci return -EIO; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci while (part && part->len) { 26262306a36Sopenharmony_ci int r; 26362306a36Sopenharmony_ci r = fill_from_part(part, ctx); 26462306a36Sopenharmony_ci if (r < 0) { 26562306a36Sopenharmony_ci od->error = r; 26662306a36Sopenharmony_ci return r; 26762306a36Sopenharmony_ci } else if (r == 0) { 26862306a36Sopenharmony_ci /* Userspace buffer is full. */ 26962306a36Sopenharmony_ci break; 27062306a36Sopenharmony_ci } else { 27162306a36Sopenharmony_ci /* 27262306a36Sopenharmony_ci * The part ran out of data. Move to the next 27362306a36Sopenharmony_ci * part. */ 27462306a36Sopenharmony_ci ctx->pos = (ctx->pos & PART_MASK) + 27562306a36Sopenharmony_ci (1 << PART_SHIFT); 27662306a36Sopenharmony_ci part = part->next; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic loff_t orangefs_dir_llseek(struct file *file, loff_t offset, 28362306a36Sopenharmony_ci int whence) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct orangefs_dir *od = file->private_data; 28662306a36Sopenharmony_ci /* 28762306a36Sopenharmony_ci * Delete the stored data so userspace sees new directory 28862306a36Sopenharmony_ci * entries. 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci if (!whence && offset < od->end) { 29162306a36Sopenharmony_ci struct orangefs_dir_part *part = od->part; 29262306a36Sopenharmony_ci while (part) { 29362306a36Sopenharmony_ci struct orangefs_dir_part *next = part->next; 29462306a36Sopenharmony_ci vfree(part); 29562306a36Sopenharmony_ci part = next; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci od->token = ORANGEFS_ITERATE_START; 29862306a36Sopenharmony_ci od->part = NULL; 29962306a36Sopenharmony_ci od->end = 1 << PART_SHIFT; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci return default_llseek(file, offset, whence); 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int orangefs_dir_iterate(struct file *file, 30562306a36Sopenharmony_ci struct dir_context *ctx) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci struct orangefs_inode_s *oi; 30862306a36Sopenharmony_ci struct orangefs_dir *od; 30962306a36Sopenharmony_ci struct dentry *dentry; 31062306a36Sopenharmony_ci int r; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci dentry = file->f_path.dentry; 31362306a36Sopenharmony_ci oi = ORANGEFS_I(dentry->d_inode); 31462306a36Sopenharmony_ci od = file->private_data; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (od->error) 31762306a36Sopenharmony_ci return od->error; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci if (ctx->pos == 0) { 32062306a36Sopenharmony_ci if (!dir_emit_dot(file, ctx)) 32162306a36Sopenharmony_ci return 0; 32262306a36Sopenharmony_ci ctx->pos++; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci if (ctx->pos == 1) { 32562306a36Sopenharmony_ci if (!dir_emit_dotdot(file, ctx)) 32662306a36Sopenharmony_ci return 0; 32762306a36Sopenharmony_ci ctx->pos = 1 << PART_SHIFT; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci /* 33162306a36Sopenharmony_ci * The seek position is in the first synthesized part but is not 33262306a36Sopenharmony_ci * valid. 33362306a36Sopenharmony_ci */ 33462306a36Sopenharmony_ci if ((ctx->pos & PART_MASK) == 0) 33562306a36Sopenharmony_ci return -EIO; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci r = 0; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci /* 34062306a36Sopenharmony_ci * Must read more if the user has sought past what has been read 34162306a36Sopenharmony_ci * so far. Stop a user who has sought past the end. 34262306a36Sopenharmony_ci */ 34362306a36Sopenharmony_ci while (od->token != ORANGEFS_ITERATE_END && 34462306a36Sopenharmony_ci ctx->pos > od->end) { 34562306a36Sopenharmony_ci r = orangefs_dir_more(oi, od, dentry); 34662306a36Sopenharmony_ci if (r) 34762306a36Sopenharmony_ci return r; 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci if (od->token == ORANGEFS_ITERATE_END && ctx->pos > od->end) 35062306a36Sopenharmony_ci return -EIO; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci /* Then try to fill if there's any left in the buffer. */ 35362306a36Sopenharmony_ci if (ctx->pos < od->end) { 35462306a36Sopenharmony_ci r = orangefs_dir_fill(oi, od, dentry, ctx); 35562306a36Sopenharmony_ci if (r) 35662306a36Sopenharmony_ci return r; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* Finally get some more and try to fill. */ 36062306a36Sopenharmony_ci if (od->token != ORANGEFS_ITERATE_END) { 36162306a36Sopenharmony_ci r = orangefs_dir_more(oi, od, dentry); 36262306a36Sopenharmony_ci if (r) 36362306a36Sopenharmony_ci return r; 36462306a36Sopenharmony_ci r = orangefs_dir_fill(oi, od, dentry, ctx); 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci return r; 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_cistatic int orangefs_dir_open(struct inode *inode, struct file *file) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct orangefs_dir *od; 37362306a36Sopenharmony_ci file->private_data = kmalloc(sizeof(struct orangefs_dir), 37462306a36Sopenharmony_ci GFP_KERNEL); 37562306a36Sopenharmony_ci if (!file->private_data) 37662306a36Sopenharmony_ci return -ENOMEM; 37762306a36Sopenharmony_ci od = file->private_data; 37862306a36Sopenharmony_ci od->token = ORANGEFS_ITERATE_START; 37962306a36Sopenharmony_ci od->part = NULL; 38062306a36Sopenharmony_ci od->end = 1 << PART_SHIFT; 38162306a36Sopenharmony_ci od->error = 0; 38262306a36Sopenharmony_ci return 0; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic int orangefs_dir_release(struct inode *inode, struct file *file) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci struct orangefs_dir *od = file->private_data; 38862306a36Sopenharmony_ci struct orangefs_dir_part *part = od->part; 38962306a36Sopenharmony_ci while (part) { 39062306a36Sopenharmony_ci struct orangefs_dir_part *next = part->next; 39162306a36Sopenharmony_ci vfree(part); 39262306a36Sopenharmony_ci part = next; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci kfree(od); 39562306a36Sopenharmony_ci return 0; 39662306a36Sopenharmony_ci} 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ciconst struct file_operations orangefs_dir_operations = { 39962306a36Sopenharmony_ci .llseek = orangefs_dir_llseek, 40062306a36Sopenharmony_ci .read = generic_read_dir, 40162306a36Sopenharmony_ci .iterate_shared = orangefs_dir_iterate, 40262306a36Sopenharmony_ci .open = orangefs_dir_open, 40362306a36Sopenharmony_ci .release = orangefs_dir_release 40462306a36Sopenharmony_ci}; 405