1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2025 Klara, Inc.
5 */
6
7#ifndef _INOTIFY_H_
8#define _INOTIFY_H_
9
10#include <sys/_types.h>
11
12/* Flags for inotify_init1(). */
13#define IN_NONBLOCK 0x00000004 /* O_NONBLOCK */
14#define IN_CLOEXEC 0x00100000 /* O_CLOEXEC */
15
16struct inotify_event {
17 int wd;
18 __uint32_t mask;
19 __uint32_t cookie;
20 __uint32_t len;
21 char name[0];
22};
23
24/* Events, set in the mask field. */
25#define IN_ACCESS 0x00000001
26#define IN_MODIFY 0x00000002
27#define IN_ATTRIB 0x00000004
28#define IN_CLOSE_WRITE 0x00000008
29#define IN_CLOSE_NOWRITE 0x00000010
30#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
31#define IN_OPEN 0x00000020
32#define IN_MOVED_FROM 0x00000040
33#define IN_MOVED_TO 0x00000080
34#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO)
35#define IN_CREATE 0x00000100
36#define IN_DELETE 0x00000200
37#define IN_DELETE_SELF 0x00000400
38#define IN_MOVE_SELF 0x00000800
39#define IN_ALL_EVENTS 0x00000fff
40
41/* Events report only for entries in a watched dir, not the dir itself. */
42#define _IN_DIR_EVENTS (IN_CLOSE_WRITE | IN_DELETE | IN_MODIFY | \
43 IN_MOVED_FROM | IN_MOVED_TO)
44
45#ifdef _KERNEL
46/*
47 * An unlink that's done as part of a rename only records IN_DELETE if the
48 * unlinked vnode itself is watched, and not when the containing directory is
49 * watched.
50 */
51#define _IN_MOVE_DELETE 0x40000000
52/*
53 * Inode link count changes only trigger IN_ATTRIB events if the inode itself is
54 * watched, and not when the containing directory is watched.
55 */
56#define _IN_ATTRIB_LINKCOUNT 0x80000000
57#endif
58
59/* Flags, set in the mask field. */
60#define IN_ONLYDIR 0x01000000
61#define IN_DONT_FOLLOW 0x02000000
62#define IN_EXCL_UNLINK 0x04000000
63#define IN_MASK_CREATE 0x10000000
64#define IN_MASK_ADD 0x20000000
65#define IN_ONESHOT 0x80000000
66#define _IN_ALL_FLAGS (IN_ONLYDIR | IN_DONT_FOLLOW | \
67 IN_EXCL_UNLINK | IN_MASK_CREATE | \
68 IN_MASK_ADD | IN_ONESHOT)
69
70/* Flags returned by the kernel. */
71#define IN_UNMOUNT 0x00002000
72#define IN_Q_OVERFLOW 0x00004000
73#define IN_IGNORED 0x00008000
74#define IN_ISDIR 0x40000000
75#define _IN_ALL_RETFLAGS (IN_Q_OVERFLOW | IN_UNMOUNT | IN_IGNORED | \
76 IN_ISDIR)
77
78#define _IN_ALIGN _Alignof(struct inotify_event)
79#define _IN_NAMESIZE(namelen) \
80 ((namelen) == 0 ? 0 : __align_up((namelen) + 1, _IN_ALIGN))
81
82#ifdef _KERNEL
83struct componentname;
84struct file;
85struct inotify_softc;
86struct thread;
87struct vnode;
88
89int inotify_create_file(struct thread *, struct file *, int, int *);
90void inotify_log(struct vnode *, const char *, size_t, int, __uint32_t);
91
92int kern_inotify_rm_watch(int, uint32_t, struct thread *);
93int kern_inotify_add_watch(int, int, const char *, uint32_t,
94 struct thread *);
95
96void vn_inotify(struct vnode *, struct vnode *, struct componentname *, int,
97 uint32_t);
98int vn_inotify_add_watch(struct vnode *, struct inotify_softc *,
99 __uint32_t, __uint32_t *, struct thread *);
100void vn_inotify_revoke(struct vnode *);
101
102/* Log an inotify event. */
103#define INOTIFY(vp, ev) do { \
104 if (__predict_false((vn_irflag_read(vp) & (VIRF_INOTIFY | \
105 VIRF_INOTIFY_PARENT)) != 0)) \
106 VOP_INOTIFY((vp), NULL, NULL, (ev), 0); \
107} while (0)
108
109/* Log an inotify event using a specific name for the vnode. */
110#define INOTIFY_NAME_LOCK(vp, dvp, cnp, ev, lock) do { \
111 if (__predict_false((vn_irflag_read(vp) & VIRF_INOTIFY) != 0 || \
112 (vn_irflag_read(dvp) & VIRF_INOTIFY) != 0)) { \
113 if (lock) \
114 vn_lock((vp), LK_SHARED | LK_RETRY); \
115 VOP_INOTIFY((vp), (dvp), (cnp), (ev), 0); \
116 if (lock) \
117 VOP_UNLOCK(vp); \
118 } \
119} while (0)
120#define INOTIFY_NAME(vp, dvp, cnp, ev) \
121 INOTIFY_NAME_LOCK((vp), (dvp), (cnp), (ev), false)
122
123extern __uint32_t inotify_rename_cookie;
124
125#define INOTIFY_MOVE(vp, fdvp, fcnp, tvp, tdvp, tcnp) do { \
126 if (__predict_false((vn_irflag_read(fdvp) & VIRF_INOTIFY) != 0 || \
127 (vn_irflag_read(tdvp) & VIRF_INOTIFY) != 0 || \
128 (vn_irflag_read(vp) & VIRF_INOTIFY) != 0)) { \
129 __uint32_t cookie; \
130 \
131 cookie = atomic_fetchadd_32(&inotify_rename_cookie, 1); \
132 VOP_INOTIFY((vp), (fdvp), (fcnp), IN_MOVED_FROM, cookie); \
133 VOP_INOTIFY((vp), (tdvp), (tcnp), IN_MOVED_TO, cookie); \
134 } \
135 if ((tvp) != NULL) \
136 INOTIFY_NAME_LOCK((tvp), (tdvp), (tcnp), \
137 _IN_MOVE_DELETE, true); \
138} while (0)
139
140#define INOTIFY_REVOKE(vp) do { \
141 if (__predict_false((vn_irflag_read(vp) & VIRF_INOTIFY) != 0)) \
142 vn_inotify_revoke((vp)); \
143} while (0)
144
145#else
146#include <sys/cdefs.h>
147
148__BEGIN_DECLS
149int inotify_init(void);
150int inotify_init1(int flags);
151int inotify_add_watch(int fd, const char *pathname, __uint32_t mask);
152int inotify_add_watch_at(int fd, int dfd, const char *pathname,
153 __uint32_t mask);
154int inotify_rm_watch(int fd, int wd);
155__END_DECLS
156#endif /* !_KERNEL */
157
158#endif /* !_INOTIFY_H_ */