#include "fanotify.h"
 
+static bool fanotify_path_equal(struct path *p1, struct path *p2)
+{
+       return p1->mnt == p2->mnt && p1->dentry == p2->dentry;
+}
+
+static inline bool fanotify_fsid_equal(__kernel_fsid_t *fsid1,
+                                      __kernel_fsid_t *fsid2)
+{
+       return fsid1->val[0] == fsid1->val[0] && fsid2->val[1] == fsid2->val[1];
+}
+
+static bool fanotify_fh_equal(struct fanotify_fh *fh1,
+                            struct fanotify_fh *fh2)
+{
+       if (fh1->type != fh2->type || fh1->len != fh2->len)
+               return false;
+
+       /* Do not merge events if we failed to encode fh */
+       if (fh1->type == FILEID_INVALID)
+               return false;
+
+       return !fh1->len ||
+               !memcmp(fanotify_fh_buf(fh1), fanotify_fh_buf(fh2), fh1->len);
+}
+
 static bool should_merge(struct fsnotify_event *old_fsn,
                         struct fsnotify_event *new_fsn)
 {
        new = FANOTIFY_E(new_fsn);
 
        if (old_fsn->objectid != new_fsn->objectid || old->pid != new->pid ||
-           old->fh_type != new->fh_type || old->fh_len != new->fh_len)
+           old->fh.type != new->fh.type)
                return false;
 
        if (fanotify_event_has_path(old)) {
-               return old->path.mnt == new->path.mnt &&
-                       old->path.dentry == new->path.dentry;
+               return fanotify_path_equal(fanotify_event_path(old),
+                                          fanotify_event_path(new));
        } else if (fanotify_event_has_fid(old)) {
                /*
                 * We want to merge many dirent events in the same dir (i.e.
                 * mask FAN_CREATE|FAN_DELETE|FAN_ONDIR if it describes mkdir+
                 * unlink pair or rmdir+create pair of events.
                 */
-               return (old->mask & FS_ISDIR) == (new->mask & FS_ISDIR) &&
-                       fanotify_fid_equal(&old->fid, &new->fid, old->fh_len);
+               if ((old->mask & FS_ISDIR) != (new->mask & FS_ISDIR))
+                       return false;
+
+               return fanotify_fsid_equal(&old->fsid, &new->fsid) &&
+                       fanotify_fh_equal(&old->fh, &new->fh);
        }
 
        /* Do not merge events if we failed to encode fid */
        return test_mask & user_mask;
 }
 
-static int fanotify_encode_fid(struct fanotify_event *event,
-                              struct inode *inode, gfp_t gfp,
-                              __kernel_fsid_t *fsid)
+static void fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
+                              gfp_t gfp)
 {
-       struct fanotify_fid *fid = &event->fid;
-       int dwords, bytes = 0;
-       int err, type;
+       int dwords, type, bytes = 0;
+       char *ext_buf = NULL;
+       void *buf = fh->buf;
+       int err;
 
-       fid->ext_fh = NULL;
        dwords = 0;
        err = -ENOENT;
        type = exportfs_encode_inode_fh(inode, NULL, &dwords, NULL);
        if (bytes > FANOTIFY_INLINE_FH_LEN) {
                /* Treat failure to allocate fh as failure to allocate event */
                err = -ENOMEM;
-               fid->ext_fh = kmalloc(bytes, gfp);
-               if (!fid->ext_fh)
+               ext_buf = kmalloc(bytes, gfp);
+               if (!ext_buf)
                        goto out_err;
+
+               *fanotify_fh_ext_buf_ptr(fh) = ext_buf;
+               buf = ext_buf;
        }
 
-       type = exportfs_encode_inode_fh(inode, fanotify_fid_fh(fid, bytes),
-                                       &dwords, NULL);
+       type = exportfs_encode_inode_fh(inode, buf, &dwords, NULL);
        err = -EINVAL;
        if (!type || type == FILEID_INVALID || bytes != dwords << 2)
                goto out_err;
 
-       fid->fsid = *fsid;
-       event->fh_len = bytes;
+       fh->type = type;
+       fh->len = bytes;
 
-       return type;
+       return;
 
 out_err:
-       pr_warn_ratelimited("fanotify: failed to encode fid (fsid=%x.%x, "
-                           "type=%d, bytes=%d, err=%i)\n",
-                           fsid->val[0], fsid->val[1], type, bytes, err);
-       kfree(fid->ext_fh);
-       fid->ext_fh = NULL;
-       event->fh_len = 0;
-
-       return FILEID_INVALID;
+       pr_warn_ratelimited("fanotify: failed to encode fid (type=%d, len=%d, err=%i)\n",
+                           type, bytes, err);
+       kfree(ext_buf);
+       *fanotify_fh_ext_buf_ptr(fh) = NULL;
+       /* Report the event without a file identifier on encode error */
+       fh->type = FILEID_INVALID;
+       fh->len = 0;
 }
 
 /*
                event->pid = get_pid(task_pid(current));
        else
                event->pid = get_pid(task_tgid(current));
-       event->fh_len = 0;
+       event->fh.len = 0;
        if (id && FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
-               /* Report the event without a file identifier on encode error */
-               event->fh_type = fanotify_encode_fid(event, id, gfp, fsid);
+               event->fsid = *fsid;
+               if (id)
+                       fanotify_encode_fh(&event->fh, id, gfp);
        } else if (path) {
-               event->fh_type = FILEID_ROOT;
+               event->fh.type = FILEID_ROOT;
                event->path = *path;
                path_get(path);
        } else {
-               event->fh_type = FILEID_INVALID;
+               event->fh.type = FILEID_INVALID;
                event->path.mnt = NULL;
                event->path.dentry = NULL;
        }
        event = FANOTIFY_E(fsn_event);
        if (fanotify_event_has_path(event))
                path_put(&event->path);
-       else if (fanotify_event_has_ext_fh(event))
-               kfree(event->fid.ext_fh);
+       else if (fanotify_fh_has_ext_buf(&event->fh))
+               kfree(fanotify_fh_ext_buf(&event->fh));
        put_pid(event->pid);
        if (fanotify_is_perm_event(event->mask)) {
                kmem_cache_free(fanotify_perm_event_cachep,
 
 
 /*
  * 3 dwords are sufficient for most local fs (64bit ino, 32bit generation).
- * For 32bit arch, fid increases the size of fanotify_event by 12 bytes and
- * fh_* fields increase the size of fanotify_event by another 4 bytes.
- * For 64bit arch, fid increases the size of fanotify_fid by 8 bytes and
- * fh_* fields are packed in a hole after mask.
+ * fh buf should be dword aligned. On 64bit arch, the ext_buf pointer is
+ * stored in either the first or last 2 dwords.
  */
-#if BITS_PER_LONG == 32
 #define FANOTIFY_INLINE_FH_LEN (3 << 2)
-#else
-#define FANOTIFY_INLINE_FH_LEN (4 << 2)
-#endif
 
-struct fanotify_fid {
-       __kernel_fsid_t fsid;
-       union {
-               unsigned char fh[FANOTIFY_INLINE_FH_LEN];
-               unsigned char *ext_fh;
-       };
-};
+struct fanotify_fh {
+       unsigned char buf[FANOTIFY_INLINE_FH_LEN];
+       u8 type;
+       u8 len;
+} __aligned(4);
+
+static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh)
+{
+       return fh->len > FANOTIFY_INLINE_FH_LEN;
+}
 
-static inline void *fanotify_fid_fh(struct fanotify_fid *fid,
-                                   unsigned int fh_len)
+static inline char **fanotify_fh_ext_buf_ptr(struct fanotify_fh *fh)
 {
-       return fh_len <= FANOTIFY_INLINE_FH_LEN ? fid->fh : fid->ext_fh;
+       BUILD_BUG_ON(__alignof__(char *) - 4 + sizeof(char *) >
+                    FANOTIFY_INLINE_FH_LEN);
+       return (char **)ALIGN((unsigned long)(fh->buf), __alignof__(char *));
 }
 
-static inline bool fanotify_fid_equal(struct fanotify_fid *fid1,
-                                     struct fanotify_fid *fid2,
-                                     unsigned int fh_len)
+static inline void *fanotify_fh_ext_buf(struct fanotify_fh *fh)
 {
-       return fid1->fsid.val[0] == fid2->fsid.val[0] &&
-               fid1->fsid.val[1] == fid2->fsid.val[1] &&
-               !memcmp(fanotify_fid_fh(fid1, fh_len),
-                       fanotify_fid_fh(fid2, fh_len), fh_len);
+       return *fanotify_fh_ext_buf_ptr(fh);
+}
+
+static inline void *fanotify_fh_buf(struct fanotify_fh *fh)
+{
+       return fanotify_fh_has_ext_buf(fh) ? fanotify_fh_ext_buf(fh) : fh->buf;
 }
 
 /*
        struct fsnotify_event fse;
        u32 mask;
        /*
-        * Those fields are outside fanotify_fid to pack fanotify_event nicely
-        * on 64bit arch and to use fh_type as an indication of whether path
-        * or fid are used in the union:
-        * FILEID_ROOT (0) for path, > 0 for fid, FILEID_INVALID for neither.
+        * With FAN_REPORT_FID, we do not hold any reference on the
+        * victim object. Instead we store its NFS file handle and its
+        * filesystem's fsid as a unique identifier.
+        */
+       __kernel_fsid_t fsid;
+       struct fanotify_fh fh;
+       /*
+        * We hold ref to this path so it may be dereferenced at any
+        * point during this object's lifetime
         */
-       u8 fh_type;
-       u8 fh_len;
-       u16 pad;
-       union {
-               /*
-                * We hold ref to this path so it may be dereferenced at any
-                * point during this object's lifetime
-                */
-               struct path path;
-               /*
-                * With FAN_REPORT_FID, we do not hold any reference on the
-                * victim object. Instead we store its NFS file handle and its
-                * filesystem's fsid as a unique identifier.
-                */
-               struct fanotify_fid fid;
-       };
+       struct path path;
        struct pid *pid;
 };
 
 static inline bool fanotify_event_has_path(struct fanotify_event *event)
 {
-       return event->fh_type == FILEID_ROOT;
+       return event->fh.type == FILEID_ROOT;
 }
 
 static inline bool fanotify_event_has_fid(struct fanotify_event *event)
 {
-       return event->fh_type != FILEID_ROOT &&
-               event->fh_type != FILEID_INVALID;
+       return event->fh.type != FILEID_ROOT &&
+              event->fh.type != FILEID_INVALID;
 }
 
-static inline bool fanotify_event_has_ext_fh(struct fanotify_event *event)
+static inline __kernel_fsid_t *fanotify_event_fsid(struct fanotify_event *event)
 {
-       return fanotify_event_has_fid(event) &&
-               event->fh_len > FANOTIFY_INLINE_FH_LEN;
+       if (fanotify_event_has_fid(event))
+               return &event->fsid;
+       else
+               return NULL;
 }
 
-static inline void *fanotify_event_fh(struct fanotify_event *event)
+static inline struct fanotify_fh *fanotify_event_object_fh(
+                                               struct fanotify_event *event)
 {
-       return fanotify_fid_fh(&event->fid, event->fh_len);
+       if (fanotify_event_has_fid(event))
+               return &event->fh;
+       else
+               return NULL;
+}
+
+static inline int fanotify_event_object_fh_len(struct fanotify_event *event)
+{
+       struct fanotify_fh *fh = fanotify_event_object_fh(event);
+
+       return fh ? fh->len : 0;
 }
 
 /*
        return container_of(fse, struct fanotify_event, fse);
 }
 
+static inline struct path *fanotify_event_path(struct fanotify_event *event)
+{
+       if (fanotify_event_has_path(event))
+               return &event->path;
+       else
+               return NULL;
+}
+
 struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
                                            struct inode *inode, u32 mask,
                                            const void *data, int data_type,
 
 
 static int fanotify_event_info_len(struct fanotify_event *event)
 {
-       if (!fanotify_event_has_fid(event))
+       int fh_len = fanotify_event_object_fh_len(event);
+
+       if (!fh_len)
                return 0;
 
        return roundup(sizeof(struct fanotify_event_info_fid) +
-                      sizeof(struct file_handle) + event->fh_len,
+                      sizeof(struct file_handle) + fh_len,
                       FANOTIFY_EVENT_ALIGN);
 }
 
 {
        struct fanotify_event_info_fid info = { };
        struct file_handle handle = { };
-       unsigned char bounce[FANOTIFY_INLINE_FH_LEN], *fh;
-       size_t fh_len = event->fh_len;
+       unsigned char bounce[FANOTIFY_INLINE_FH_LEN], *fh_buf;
+       struct fanotify_fh *fh = fanotify_event_object_fh(event);
+       size_t fh_len = fh->len;
        size_t len = fanotify_event_info_len(event);
 
        if (!len)
        /* Copy event info fid header followed by vaiable sized file handle */
        info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID;
        info.hdr.len = len;
-       info.fsid = event->fid.fsid;
+       info.fsid = *fanotify_event_fsid(event);
        if (copy_to_user(buf, &info, sizeof(info)))
                return -EFAULT;
 
        buf += sizeof(info);
        len -= sizeof(info);
-       handle.handle_type = event->fh_type;
+       handle.handle_type = fh->type;
        handle.handle_bytes = fh_len;
        if (copy_to_user(buf, &handle, sizeof(handle)))
                return -EFAULT;
         * For an inline fh, copy through stack to exclude the copy from
         * usercopy hardening protections.
         */
-       fh = fanotify_event_fh(event);
+       fh_buf = fanotify_fh_buf(fh);
        if (fh_len <= FANOTIFY_INLINE_FH_LEN) {
-               memcpy(bounce, fh, fh_len);
-               fh = bounce;
+               memcpy(bounce, fh_buf, fh_len);
+               fh_buf = bounce;
        }
-       if (copy_to_user(buf, fh, fh_len))
+       if (copy_to_user(buf, fh_buf, fh_len))
                return -EFAULT;
 
        /* Pad with 0's */
 {
        struct fanotify_event_metadata metadata;
        struct fanotify_event *event;
+       struct path *path;
        struct file *f = NULL;
        int ret, fd = FAN_NOFD;
 
        pr_debug("%s: group=%p event=%p\n", __func__, group, fsn_event);
 
        event = container_of(fsn_event, struct fanotify_event, fse);
+       path = fanotify_event_path(event);
        metadata.event_len = FAN_EVENT_METADATA_LEN;
        metadata.metadata_len = FAN_EVENT_METADATA_LEN;
        metadata.vers = FANOTIFY_METADATA_VERSION;
        metadata.mask = event->mask & FANOTIFY_OUTGOING_EVENTS;
        metadata.pid = pid_vnr(event->pid);
 
-       if (fanotify_event_has_path(event)) {
-               struct path *path = &event->path;
-
-               if (path->mnt && path->dentry) {
-                       fd = create_fd(group, path, &f);
-                       if (fd < 0)
-                               return fd;
-               }
-       } else if (fanotify_event_has_fid(event)) {
+       if (fanotify_event_has_fid(event)) {
                metadata.event_len += fanotify_event_info_len(event);
+       } else if (path && path->mnt && path->dentry) {
+               fd = create_fd(group, path, &f);
+               if (fd < 0)
+                       return fd;
        }
        metadata.fd = fd;