Remove the tree of active dentries from the cachefiles_cache struct and instead set a flag, S_CACHE_FILE, on the backing inode to indicate that this file is in use by the kernel so as to ward off other kernel users. This simplifies the code a lot and also prevents two overlain caches from fighting with each other. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/cachefiles/daemon.c | 4 fs/cachefiles/interface.c | 20 -- fs/cachefiles/internal.h | 10 - fs/cachefiles/namei.c | 375 +++++++------------------------------ include/trace/events/cachefiles.h | 29 --- 5 files changed, 78 insertions(+), 360 deletions(-) diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c index 752c1e43416f..8a937d6d5e22 100644 --- a/fs/cachefiles/daemon.c +++ b/fs/cachefiles/daemon.c @@ -102,8 +102,6 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file) } mutex_init(&cache->daemon_mutex); - cache->active_nodes = RB_ROOT; - rwlock_init(&cache->active_lock); init_waitqueue_head(&cache->daemon_pollwq); /* set default caching limits @@ -138,8 +136,6 @@ static int cachefiles_daemon_release(struct inode *inode, struct file *file) cachefiles_daemon_unbind(cache); - ASSERT(!cache->active_nodes.rb_node); - /* clean up the control file interface */ cache->cachefilesd = NULL; file->private_data = NULL; diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 99f42d216ef7..b868afb970ad 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -36,7 +36,6 @@ static struct fscache_object *cachefiles_alloc_object( ASSERTCMP(object->backer, ==, NULL); - BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); atomic_set(&object->usage, 1); fscache_object_init(&object->fscache, cookie, &cache->cache); @@ -74,7 +73,6 @@ static struct fscache_object *cachefiles_alloc_object( nomem_key: kfree(buffer); nomem_buffer: - BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); kmem_cache_free(cachefiles_object_jar, object); fscache_object_destroyed(&cache->cache); nomem_object: @@ -190,8 +188,6 @@ static void cachefiles_drop_object(struct fscache_object *_object) struct cachefiles_object *object; struct cachefiles_cache *cache; const struct cred *saved_cred; - struct inode *inode; - blkcnt_t i_blocks = 0; ASSERT(_object); @@ -218,10 +214,6 @@ static void cachefiles_drop_object(struct fscache_object *_object) _object != cache->cache.fsdef ) { _debug("- retire object OBJ%x", object->fscache.debug_id); - inode = d_backing_inode(object->dentry); - if (inode) - i_blocks = inode->i_blocks; - cachefiles_begin_secure(cache, &saved_cred); cachefiles_delete_object(cache, object); cachefiles_end_secure(cache, saved_cred); @@ -231,14 +223,11 @@ static void cachefiles_drop_object(struct fscache_object *_object) if (object->backer != object->dentry) dput(object->backer); object->backer = NULL; - } - /* note that the object is now inactive */ - if (test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)) - cachefiles_mark_object_inactive(cache, object, i_blocks); - - dput(object->dentry); - object->dentry = NULL; + cachefiles_unmark_inode_in_use(object, object->dentry); + dput(object->dentry); + object->dentry = NULL; + } _leave(""); } @@ -274,7 +263,6 @@ static void cachefiles_put_object(struct fscache_object *_object, if (u == 0) { _debug("- kill object OBJ%x", object->fscache.debug_id); - ASSERT(!test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); ASSERTCMP(object->fscache.parent, ==, NULL); ASSERTCMP(object->backer, ==, NULL); ASSERTCMP(object->dentry, ==, NULL); diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index a5d48f271ce1..f8f308ce7385 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -38,12 +38,9 @@ struct cachefiles_object { struct dentry *dentry; /* the file/dir representing this object */ struct dentry *backer; /* backing file */ loff_t i_size; /* object size */ - unsigned long flags; -#define CACHEFILES_OBJECT_ACTIVE 0 /* T if marked active */ atomic_t usage; /* object usage count */ uint8_t type; /* object type */ uint8_t new; /* T if object new */ - struct rb_node active_node; /* link in active tree (dentry is key) */ }; extern struct kmem_cache *cachefiles_object_jar; @@ -59,8 +56,6 @@ struct cachefiles_cache { const struct cred *cache_cred; /* security override for accessing cache */ struct mutex daemon_mutex; /* command serialisation mutex */ wait_queue_head_t daemon_pollwq; /* poll waitqueue for daemon */ - struct rb_root active_nodes; /* active nodes (can't be culled) */ - rwlock_t active_lock; /* lock for active_nodes */ atomic_t gravecounter; /* graveyard uniquifier */ atomic_t f_released; /* number of objects released lately */ atomic_long_t b_released; /* number of blocks released lately */ @@ -126,9 +121,8 @@ extern char *cachefiles_cook_key(const u8 *raw, int keylen, uint8_t type); /* * namei.c */ -extern void cachefiles_mark_object_inactive(struct cachefiles_cache *cache, - struct cachefiles_object *object, - blkcnt_t i_blocks); +extern void cachefiles_unmark_inode_in_use(struct cachefiles_object *object, + struct dentry *dentry); extern int cachefiles_delete_object(struct cachefiles_cache *cache, struct cachefiles_object *object); extern int cachefiles_walk_to_object(struct cachefiles_object *parent, diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 924042e8cced..818d1bca1904 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -21,251 +21,51 @@ #define CACHEFILES_KEYBUF_SIZE 512 /* - * dump debugging info about an object + * Mark the backing file as being a cache file if it's not already in use so. */ -static noinline -void __cachefiles_printk_object(struct cachefiles_object *object, - const char *prefix) +static bool cachefiles_mark_inode_in_use(struct cachefiles_object *object, + struct dentry *dentry) { - struct fscache_cookie *cookie; - const u8 *k; - unsigned loop; - - pr_err("%sobject: OBJ%x\n", prefix, object->fscache.debug_id); - pr_err("%sobjstate=%s fl=%lx wbusy=%x ev=%lx[%lx]\n", - prefix, object->fscache.state->name, - object->fscache.flags, work_busy(&object->fscache.work), - object->fscache.events, object->fscache.event_mask); - pr_err("%sops=%u\n", - prefix, object->fscache.n_ops); - pr_err("%sparent=%p\n", - prefix, object->fscache.parent); - - spin_lock(&object->fscache.lock); - cookie = object->fscache.cookie; - if (cookie) { - pr_err("%scookie=%p [pr=%p fl=%lx]\n", - prefix, - object->fscache.cookie, - object->fscache.cookie->parent, - object->fscache.cookie->flags); - pr_err("%skey=[%u] '", prefix, cookie->key_len); - k = (cookie->key_len <= sizeof(cookie->inline_key)) ? - cookie->inline_key : cookie->key; - for (loop = 0; loop < cookie->key_len; loop++) - pr_cont("%02x", k[loop]); - pr_cont("'\n"); - } else { - pr_err("%scookie=NULL\n", prefix); - } - spin_unlock(&object->fscache.lock); -} - -/* - * dump debugging info about a pair of objects - */ -static noinline void cachefiles_printk_object(struct cachefiles_object *object, - struct cachefiles_object *xobject) -{ - if (object) - __cachefiles_printk_object(object, ""); - if (xobject) - __cachefiles_printk_object(xobject, "x"); -} - -/* - * mark the owner of a dentry, if there is one, to indicate that that dentry - * has been preemptively deleted - * - the caller must hold the i_mutex on the dentry's parent as required to - * call vfs_unlink(), vfs_rmdir() or vfs_rename() - */ -static void cachefiles_mark_object_buried(struct cachefiles_cache *cache, - struct dentry *dentry, - enum fscache_why_object_killed why) -{ - struct cachefiles_object *object; - struct rb_node *p; - - _enter(",'%pd'", dentry); - - write_lock(&cache->active_lock); - - p = cache->active_nodes.rb_node; - while (p) { - object = rb_entry(p, struct cachefiles_object, active_node); - if (object->dentry > dentry) - p = p->rb_left; - else if (object->dentry < dentry) - p = p->rb_right; - else - goto found_dentry; - } - - write_unlock(&cache->active_lock); - trace_cachefiles_mark_buried(NULL, dentry, why); - _leave(" [no owner]"); - return; + struct inode *inode = d_backing_inode(dentry); + bool can_use = false; - /* found the dentry for */ -found_dentry: - kdebug("preemptive burial: OBJ%x [%s] %p", - object->fscache.debug_id, - object->fscache.state->name, - dentry); + _enter(",%p", object); - trace_cachefiles_mark_buried(object, dentry, why); + inode_lock(inode); - if (fscache_object_is_live(&object->fscache)) { - pr_err("\n"); - pr_err("Error: Can't preemptively bury live object\n"); - cachefiles_printk_object(object, NULL); + if (!(inode->i_flags & S_CACHE_FILE)) { + inode->i_flags |= S_CACHE_FILE; + trace_cachefiles_mark_active(object, dentry); + can_use = true; } else { - if (why != FSCACHE_OBJECT_IS_STALE) - fscache_object_mark_killed(&object->fscache, why); + pr_notice("cachefiles: Inode already in use: %pd\n", dentry); } - write_unlock(&cache->active_lock); - _leave(" [owner marked]"); + inode_unlock(inode); + return can_use; } /* - * record the fact that an object is now active + * Unmark a backing inode. */ -static int cachefiles_mark_object_active(struct cachefiles_cache *cache, - struct cachefiles_object *object) +void cachefiles_unmark_inode_in_use(struct cachefiles_object *object, + struct dentry *dentry) { - struct cachefiles_object *xobject; - struct rb_node **_p, *_parent = NULL; - struct dentry *dentry; - - _enter(",%p", object); - -try_again: - write_lock(&cache->active_lock); - - dentry = object->dentry; - trace_cachefiles_mark_active(object, dentry); - - if (test_and_set_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)) { - pr_err("Error: Object already active\n"); - cachefiles_printk_object(object, NULL); - BUG(); - } - - _p = &cache->active_nodes.rb_node; - while (*_p) { - _parent = *_p; - xobject = rb_entry(_parent, - struct cachefiles_object, active_node); - - ASSERT(xobject != object); - - if (xobject->dentry > dentry) - _p = &(*_p)->rb_left; - else if (xobject->dentry < dentry) - _p = &(*_p)->rb_right; - else - goto wait_for_old_object; - } - - rb_link_node(&object->active_node, _parent, _p); - rb_insert_color(&object->active_node, &cache->active_nodes); - - write_unlock(&cache->active_lock); - _leave(" = 0"); - return 0; - - /* an old object from a previous incarnation is hogging the slot - we - * need to wait for it to be destroyed */ -wait_for_old_object: - trace_cachefiles_wait_active(object, dentry, xobject); - clear_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags); - - if (fscache_object_is_live(&xobject->fscache)) { - pr_err("\n"); - pr_err("Error: Unexpected object collision\n"); - cachefiles_printk_object(object, xobject); - } - atomic_inc(&xobject->usage); - write_unlock(&cache->active_lock); - - if (test_bit(CACHEFILES_OBJECT_ACTIVE, &xobject->flags)) { - wait_queue_head_t *wq; - - signed long timeout = 60 * HZ; - wait_queue_entry_t wait; - bool requeue; - - /* if the object we're waiting for is queued for processing, - * then just put ourselves on the queue behind it */ - if (work_pending(&xobject->fscache.work)) { - _debug("queue OBJ%x behind OBJ%x immediately", - object->fscache.debug_id, - xobject->fscache.debug_id); - goto requeue; - } - - /* otherwise we sleep until either the object we're waiting for - * is done, or the fscache_object is congested */ - wq = bit_waitqueue(&xobject->flags, CACHEFILES_OBJECT_ACTIVE); - init_wait(&wait); - requeue = false; - do { - prepare_to_wait(wq, &wait, TASK_UNINTERRUPTIBLE); - if (!test_bit(CACHEFILES_OBJECT_ACTIVE, &xobject->flags)) - break; - - requeue = fscache_object_sleep_till_congested(&timeout); - } while (timeout > 0 && !requeue); - finish_wait(wq, &wait); - - if (requeue && - test_bit(CACHEFILES_OBJECT_ACTIVE, &xobject->flags)) { - _debug("queue OBJ%x behind OBJ%x after wait", - object->fscache.debug_id, - xobject->fscache.debug_id); - goto requeue; - } - - if (timeout <= 0) { - pr_err("\n"); - pr_err("Error: Overlong wait for old active object to go away\n"); - cachefiles_printk_object(object, xobject); - goto requeue; - } - } - - ASSERT(!test_bit(CACHEFILES_OBJECT_ACTIVE, &xobject->flags)); - - cache->cache.ops->put_object(&xobject->fscache, - (enum fscache_obj_ref_trace)cachefiles_obj_put_wait_retry); - goto try_again; + struct inode *inode = d_backing_inode(dentry); -requeue: - cache->cache.ops->put_object(&xobject->fscache, - (enum fscache_obj_ref_trace)cachefiles_obj_put_wait_timeo); - _leave(" = -ETIMEDOUT"); - return -ETIMEDOUT; + inode_lock(inode); + inode->i_flags &= ~S_CACHE_FILE; + inode_unlock(inode); + trace_cachefiles_mark_inactive(object, dentry, inode); } /* * Mark an object as being inactive. */ -void cachefiles_mark_object_inactive(struct cachefiles_cache *cache, - struct cachefiles_object *object, - blkcnt_t i_blocks) +static void cachefiles_mark_object_inactive(struct cachefiles_cache *cache, + struct cachefiles_object *object) { - struct dentry *dentry = object->dentry; - struct inode *inode = d_backing_inode(dentry); - - trace_cachefiles_mark_inactive(object, dentry, inode); - - write_lock(&cache->active_lock); - rb_erase(&object->active_node, &cache->active_nodes); - clear_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags); - write_unlock(&cache->active_lock); - - wake_up_bit(&object->flags, CACHEFILES_OBJECT_ACTIVE); + blkcnt_t i_blocks = d_backing_inode(object->dentry)->i_blocks; /* This object can now be culled, so we need to let the daemon know * that there is something it can remove if it needs to. @@ -286,7 +86,6 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, struct cachefiles_object *object, struct dentry *dir, struct dentry *rep, - bool preemptive, enum fscache_why_object_killed why) { struct dentry *grave, *trap; @@ -310,9 +109,6 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, } else { trace_cachefiles_unlink(object, rep, why); ret = vfs_unlink(d_inode(dir), rep, NULL); - - if (preemptive) - cachefiles_mark_object_buried(cache, rep, why); } inode_unlock(d_inode(dir)); @@ -373,8 +169,7 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, return -ENOMEM; } - cachefiles_io_error(cache, "Lookup error %ld", - PTR_ERR(grave)); + cachefiles_io_error(cache, "Lookup error %ld", PTR_ERR(grave)); return -EIO; } @@ -416,9 +211,6 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, if (ret != 0 && ret != -ENOMEM) cachefiles_io_error(cache, "Rename failed with error %d", ret); - - if (preemptive) - cachefiles_mark_object_buried(cache, rep, why); } unlock_rename(cache->graveyard, dir); @@ -446,26 +238,18 @@ int cachefiles_delete_object(struct cachefiles_cache *cache, inode_lock_nested(d_inode(dir), I_MUTEX_PARENT); - if (test_bit(FSCACHE_OBJECT_KILLED_BY_CACHE, &object->fscache.flags)) { - /* object allocation for the same key preemptively deleted this - * object's file so that it could create its own file */ - _debug("object preemptively buried"); + /* We need to check that our parent is _still_ our parent - it may have + * been renamed. + */ + if (dir == object->dentry->d_parent) { + ret = cachefiles_bury_object(cache, object, dir, object->dentry, + FSCACHE_OBJECT_WAS_RETIRED); + } else { + /* It got moved, presumably by cachefilesd culling it, so it's + * no longer in the key path and we can ignore it. + */ inode_unlock(d_inode(dir)); ret = 0; - } else { - /* we need to check that our parent is _still_ our parent - it - * may have been renamed */ - if (dir == object->dentry->d_parent) { - ret = cachefiles_bury_object(cache, object, dir, - object->dentry, false, - FSCACHE_OBJECT_WAS_RETIRED); - } else { - /* it got moved, presumably by cachefilesd culling it, - * so it's no longer in the key path and we can ignore - * it */ - inode_unlock(d_inode(dir)); - ret = 0; - } } dput(dir); @@ -487,6 +271,7 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, struct path path; unsigned long start; const char *name; + bool marked = false; int ret, nlen; _enter("OBJ%x{%p},OBJ%x,%s,", @@ -529,6 +314,7 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, cachefiles_hist(cachefiles_lookup_histogram, start); if (IS_ERR(next)) { trace_cachefiles_lookup(object, next, NULL); + ret = PTR_ERR(next); goto lookup_error; } @@ -628,6 +414,13 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, /* we've found the object we were looking for */ object->dentry = next; + /* note that we're now using this object */ + if (!cachefiles_mark_inode_in_use(object, object->dentry)) { + ret = -EBUSY; + goto check_error_unlock; + } + marked = true; + /* if we've found that the terminal object exists, then we need to * check its attributes and delete it if it's out of date */ if (!object->new) { @@ -640,13 +433,12 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, object->dentry = NULL; ret = cachefiles_bury_object(cache, object, dir, next, - true, FSCACHE_OBJECT_IS_STALE); dput(next); next = NULL; if (ret < 0) - goto delete_error; + goto error_out2; _debug("redo lookup"); fscache_object_retrying_stale(&object->fscache); @@ -654,16 +446,10 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, } } - /* note that we're now using this object */ - ret = cachefiles_mark_object_active(cache, object); - inode_unlock(d_inode(dir)); dput(dir); dir = NULL; - if (ret == -ETIMEDOUT) - goto mark_active_timed_out; - _debug("=== OBTAINED_OBJECT ==="); if (object->new) { @@ -712,26 +498,19 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, cachefiles_io_error(cache, "Create/mkdir failed"); goto error; -mark_active_timed_out: - _debug("mark active timed out"); - goto release_dentry; - +check_error_unlock: + inode_unlock(d_inode(dir)); + dput(dir); check_error: - _debug("check error %d", ret); - cachefiles_mark_object_inactive( - cache, object, d_backing_inode(object->dentry)->i_blocks); -release_dentry: + if (marked) + cachefiles_unmark_inode_in_use(object, object->dentry); + cachefiles_mark_object_inactive(cache, object); dput(object->dentry); object->dentry = NULL; goto error_out; -delete_error: - _debug("delete error %d", ret); - goto error_out2; - lookup_error: - _debug("lookup error %ld", PTR_ERR(next)); - ret = PTR_ERR(next); + _debug("lookup error %d", ret); if (ret == -EIO) cachefiles_io_error(cache, "Lookup failed"); next = NULL; @@ -861,8 +640,6 @@ static struct dentry *cachefiles_check_active(struct cachefiles_cache *cache, struct dentry *dir, char *filename) { - struct cachefiles_object *object; - struct rb_node *_n; struct dentry *victim; unsigned long start; int ret; @@ -892,34 +669,9 @@ static struct dentry *cachefiles_check_active(struct cachefiles_cache *cache, return ERR_PTR(-ENOENT); } - /* check to see if we're using this object */ - read_lock(&cache->active_lock); - - _n = cache->active_nodes.rb_node; - - while (_n) { - object = rb_entry(_n, struct cachefiles_object, active_node); - - if (object->dentry > victim) - _n = _n->rb_left; - else if (object->dentry < victim) - _n = _n->rb_right; - else - goto object_in_use; - } - - read_unlock(&cache->active_lock); - //_leave(" = %p", victim); return victim; -object_in_use: - read_unlock(&cache->active_lock); - inode_unlock(d_inode(dir)); - dput(victim); - //_leave(" = -EBUSY [in use]"); - return ERR_PTR(-EBUSY); - lookup_error: inode_unlock(d_inode(dir)); ret = PTR_ERR(victim); @@ -948,6 +700,7 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir, char *filename) { struct dentry *victim; + struct inode *inode; int ret; _enter(",%pd/,%s", dir, filename); @@ -956,6 +709,19 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir, if (IS_ERR(victim)) return PTR_ERR(victim); + /* check to see if someone is using this object */ + inode = d_inode(victim); + inode_lock(inode); + if (inode->i_flags & S_CACHE_FILE) { + ret = -EBUSY; + } else { + inode->i_flags |= S_CACHE_FILE; + ret = 0; + } + inode_unlock(inode); + if (ret < 0) + goto error_unlock; + _debug("victim -> %p %s", victim, d_backing_inode(victim) ? "positive" : "negative"); @@ -971,7 +737,7 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir, /* actually remove the victim (drops the dir mutex) */ _debug("bury"); - ret = cachefiles_bury_object(cache, NULL, dir, victim, false, + ret = cachefiles_bury_object(cache, NULL, dir, victim, FSCACHE_OBJECT_WAS_CULLED); if (ret < 0) goto error; @@ -1008,6 +774,7 @@ int cachefiles_check_in_use(struct cachefiles_cache *cache, struct dentry *dir, char *filename) { struct dentry *victim; + int ret = 0; //_enter(",%pd/,%s", // dir, filename); @@ -1017,7 +784,9 @@ int cachefiles_check_in_use(struct cachefiles_cache *cache, struct dentry *dir, return PTR_ERR(victim); inode_unlock(d_inode(dir)); + if (d_inode(victim)->i_flags & S_CACHE_FILE) + ret = -EBUSY; dput(victim); //_leave(" = 0"); - return 0; + return ret; } diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index 9a448fe9355d..c877035c2946 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -237,35 +237,6 @@ TRACE_EVENT(cachefiles_mark_active, __entry->obj, __entry->de) ); -TRACE_EVENT(cachefiles_wait_active, - TP_PROTO(struct cachefiles_object *obj, - struct dentry *de, - struct cachefiles_object *xobj), - - TP_ARGS(obj, de, xobj), - - /* Note that obj may be NULL */ - TP_STRUCT__entry( - __field(unsigned int, obj ) - __field(unsigned int, xobj ) - __field(struct dentry *, de ) - __field(u16, flags ) - __field(u16, fsc_flags ) - ), - - TP_fast_assign( - __entry->obj = obj->fscache.debug_id; - __entry->de = de; - __entry->xobj = xobj->fscache.debug_id; - __entry->flags = xobj->flags; - __entry->fsc_flags = xobj->fscache.flags; - ), - - TP_printk("o=%08x d=%p wo=%08x wf=%x wff=%x", - __entry->obj, __entry->de, __entry->xobj, - __entry->flags, __entry->fsc_flags) - ); - TRACE_EVENT(cachefiles_mark_inactive, TP_PROTO(struct cachefiles_object *obj, struct dentry *de,