← Documents Documentation/core-api/watch_queue.rst GitHub 원문 ↗

Linux 6.18.37 · Core API

General notification mechanism

Pipe 기반 watch queue의 message format, source와 subscription 관리, notification posting, filtering 및 userspace 소비 절차를 설명합니다.

Source pathDocumentation/core-api/watch_queue.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

watch_queue.rst:1-343

Watch queue는 특별한 pipe의 ring buffer에 kernel notification을 기록하고 userspace가 `read()`로 소비하게 합니다. Producer는 consumer를 기다리지 않으며 overflow나 buffer 부족은 loss meta notification으로 알립니다.

Watch list는 notification source의 subscriber 집합이고, watch는 source와 output watch queue를 연결합니다. Source object ID와 userspace watch ID를 별도로 사용해 대상과 subscription을 구분합니다.

Message header의 `type`, `subtype`, `info`에는 source 종류, record 종류, 길이, watch ID 및 type별 정보가 들어갑니다. Queue filter는 이 값과 subtype bitmask를 기준으로 event를 선택합니다.

Userspace는 pipe를 만든 뒤 queue 크기와 filter를 ioctl로 설정하고, keyctl 같은 source별 API로 subscription을 연결한 다음 variable-length record를 순서대로 검증하며 읽습니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ==============================
2 General notification mechanism
3 ==============================
4
5 The general notification mechanism is built on top of the standard pipe driver
6 whereby it effectively splices notification messages from the kernel into pipes
7 opened by userspace. This can be used in conjunction with::
8
9 * Key/keyring notifications
10
11
12 The notifications buffers can be enabled by:
13
14 "General setup"/"General notification queue"
15 (CONFIG_WATCH_QUEUE)
16
17 This document has the following sections:
18
19 .. contents:: :local:
20
21
22 Overview
23 ========
24
25 This facility appears as a pipe that is opened in a special mode. The pipe's
26 internal ring buffer is used to hold messages that are generated by the kernel.
27 These messages are then read out by read(). Splice and similar are disabled on
28 such pipes due to them wanting to, under some circumstances, revert their
29 additions to the ring - which might end up interleaved with notification
30 messages.
31
32 The owner of the pipe has to tell the kernel which sources it would like to
33 watch through that pipe. Only sources that have been connected to a pipe will
34 insert messages into it. Note that a source may be bound to multiple pipes and
35 insert messages into all of them simultaneously.
36
37 Filters may also be emplaced on a pipe so that certain source types and
38 subevents can be ignored if they're not of interest.
39
40 A message will be discarded if there isn't a slot available in the ring or if
41 no preallocated message buffer is available. In both of these cases, read()
42 will insert a WATCH_META_LOSS_NOTIFICATION message into the output buffer after
43 the last message currently in the buffer has been read.
44
45 Note that when producing a notification, the kernel does not wait for the
46 consumers to collect it, but rather just continues on. This means that
47 notifications can be generated whilst spinlocks are held and also protects the
48 kernel from being held up indefinitely by a userspace malfunction.
49
50
51 Message Structure
52 =================
53
54 Notification messages begin with a short header::
55
56 struct watch_notification {
57 __u32 type:24;
58 __u32 subtype:8;
59 __u32 info;
60 };
61
62 "type" indicates the source of the notification record and "subtype" indicates
63 the type of record from that source (see the Watch Sources section below). The
64 type may also be "WATCH_TYPE_META". This is a special record type generated
65 internally by the watch queue itself. There are two subtypes:
66
67 * WATCH_META_REMOVAL_NOTIFICATION
68 * WATCH_META_LOSS_NOTIFICATION
69
70 The first indicates that an object on which a watch was installed was removed
71 or destroyed and the second indicates that some messages have been lost.
72
73 "info" indicates a bunch of things, including:
74
75 * The length of the message in bytes, including the header (mask with
76 WATCH_INFO_LENGTH and shift by WATCH_INFO_LENGTH__SHIFT). This indicates
77 the size of the record, which may be between 8 and 127 bytes.
78
79 * The watch ID (mask with WATCH_INFO_ID and shift by WATCH_INFO_ID__SHIFT).
80 This indicates that caller's ID of the watch, which may be between 0
81 and 255. Multiple watches may share a queue, and this provides a means to
82 distinguish them.
83
84 * A type-specific field (WATCH_INFO_TYPE_INFO). This is set by the
85 notification producer to indicate some meaning specific to the type and
86 subtype.
87
88 Everything in info apart from the length can be used for filtering.
89
90 The header can be followed by supplementary information. The format of this is
91 at the discretion is defined by the type and subtype.
92
93
94 Watch List (Notification Source) API
95 ====================================
96
97 A "watch list" is a list of watchers that are subscribed to a source of
98 notifications. A list may be attached to an object (say a key or a superblock)
99 or may be global (say for device events). From a userspace perspective, a
100 non-global watch list is typically referred to by reference to the object it
101 belongs to (such as using KEYCTL_NOTIFY and giving it a key serial number to
102 watch that specific key).
103
104 To manage a watch list, the following functions are provided:
105
106 * ::
107
108 void init_watch_list(struct watch_list *wlist,
109 void (*release_watch)(struct watch *wlist));
110
111 Initialise a watch list. If ``release_watch`` is not NULL, then this
112 indicates a function that should be called when the watch_list object is
113 destroyed to discard any references the watch list holds on the watched
114 object.
115
116 * ``void remove_watch_list(struct watch_list *wlist);``
117
118 This removes all of the watches subscribed to a watch_list and frees them
119 and then destroys the watch_list object itself.
120
121
122 Watch Queue (Notification Output) API
123 =====================================
124
125 A "watch queue" is the buffer allocated by an application that notification
126 records will be written into. The workings of this are hidden entirely inside
127 of the pipe device driver, but it is necessary to gain a reference to it to set
128 a watch. These can be managed with:
129
130 * ``struct watch_queue *get_watch_queue(int fd);``
131
132 Since watch queues are indicated to the kernel by the fd of the pipe that
133 implements the buffer, userspace must hand that fd through a system call.
134 This can be used to look up an opaque pointer to the watch queue from the
135 system call.
136
137 * ``void put_watch_queue(struct watch_queue *wqueue);``
138
139 This discards the reference obtained from ``get_watch_queue()``.
140
141
142 Watch Subscription API
143 ======================
144
145 A "watch" is a subscription on a watch list, indicating the watch queue, and
146 thus the buffer, into which notification records should be written. The watch
147 queue object may also carry filtering rules for that object, as set by
148 userspace. Some parts of the watch struct can be set by the driver::
149
150 struct watch {
151 union {
152 u32 info_id; /* ID to be OR'd in to info field */
153 ...
154 };
155 void *private; /* Private data for the watched object */
156 u64 id; /* Internal identifier */
157 ...
158 };
159
160 The ``info_id`` value should be an 8-bit number obtained from userspace and
161 shifted by WATCH_INFO_ID__SHIFT. This is OR'd into the WATCH_INFO_ID field of
162 struct watch_notification::info when and if the notification is written into
163 the associated watch queue buffer.
164
165 The ``private`` field is the driver's data associated with the watch_list and
166 is cleaned up by the ``watch_list::release_watch()`` method.
167
168 The ``id`` field is the source's ID. Notifications that are posted with a
169 different ID are ignored.
170
171 The following functions are provided to manage watches:
172
173 * ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``
174
175 Initialise a watch object, setting its pointer to the watch queue, using
176 appropriate barriering to avoid lockdep complaints.
177
178 * ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``
179
180 Subscribe a watch to a watch list (notification source). The
181 driver-settable fields in the watch struct must have been set before this
182 is called.
183
184 * ::
185
186 int remove_watch_from_object(struct watch_list *wlist,
187 struct watch_queue *wqueue,
188 u64 id, false);
189
190 Remove a watch from a watch list, where the watch must match the specified
191 watch queue (``wqueue``) and object identifier (``id``). A notification
192 (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue to
193 indicate that the watch got removed.
194
195 * ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``
196
197 Remove all the watches from a watch list. It is expected that this will be
198 called preparatory to destruction and that the watch list will be
199 inaccessible to new watches by this point. A notification
200 (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue of each
201 subscribed watch to indicate that the watch got removed.
202
203
204 Notification Posting API
205 ========================
206
207 To post a notification to watch list so that the subscribed watches can see it,
208 the following function should be used::
209
210 void post_watch_notification(struct watch_list *wlist,
211 struct watch_notification *n,
212 const struct cred *cred,
213 u64 id);
214
215 The notification should be preformatted and a pointer to the header (``n``)
216 should be passed in. The notification may be larger than this and the size in
217 units of buffer slots is noted in ``n->info & WATCH_INFO_LENGTH``.
218
219 The ``cred`` struct indicates the credentials of the source (subject) and is
220 passed to the LSMs, such as SELinux, to allow or suppress the recording of the
221 note in each individual queue according to the credentials of that queue
222 (object).
223
224 The ``id`` is the ID of the source object (such as the serial number on a key).
225 Only watches that have the same ID set in them will see this notification.
226
227
228 Watch Sources
229 =============
230
231 Any particular buffer can be fed from multiple sources. Sources include:
232
233 * WATCH_TYPE_KEY_NOTIFY
234
235 Notifications of this type indicate changes to keys and keyrings, including
236 the changes of keyring contents or the attributes of keys.
237
238 See Documentation/security/keys/core.rst for more information.
239
240
241 Event Filtering
242 ===============
243
244 Once a watch queue has been created, a set of filters can be applied to limit
245 the events that are received using::
246
247 struct watch_notification_filter filter = {
248 ...
249 };
250 ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
251
252 The filter description is a variable of type::
253
254 struct watch_notification_filter {
255 __u32 nr_filters;
256 __u32 __reserved;
257 struct watch_notification_type_filter filters[];
258 };
259
260 Where "nr_filters" is the number of filters in filters[] and "__reserved"
261 should be 0. The "filters" array has elements of the following type::
262
263 struct watch_notification_type_filter {
264 __u32 type;
265 __u32 info_filter;
266 __u32 info_mask;
267 __u32 subtype_filter[8];
268 };
269
270 Where:
271
272 * ``type`` is the event type to filter for and should be something like
273 "WATCH_TYPE_KEY_NOTIFY"
274
275 * ``info_filter`` and ``info_mask`` act as a filter on the info field of the
276 notification record. The notification is only written into the buffer if::
277
278 (watch.info & info_mask) == info_filter
279
280 This could be used, for example, to ignore events that are not exactly on
281 the watched point in a mount tree.
282
283 * ``subtype_filter`` is a bitmask indicating the subtypes that are of
284 interest. Bit 0 of subtype_filter[0] corresponds to subtype 0, bit 1 to
285 subtype 1, and so on.
286
287 If the argument to the ioctl() is NULL, then the filters will be removed and
288 all events from the watched sources will come through.
289
290
291 Userspace Code Example
292 ======================
293
294 A buffer is created with something like the following::
295
296 pipe2(fds, O_TMPFILE);
297 ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
298
299 It can then be set to receive keyring change notifications::
300
301 keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
302
303 The notifications can then be consumed by something like the following::
304
305 static void consumer(int rfd, struct watch_queue_buffer *buf)
306 {
307 unsigned char buffer[128];
308 ssize_t buf_len;
309
310 while (buf_len = read(rfd, buffer, sizeof(buffer)),
311 buf_len > 0
312 ) {
313 void *p = buffer;
314 void *end = buffer + buf_len;
315 while (p < end) {
316 union {
317 struct watch_notification n;
318 unsigned char buf1[128];
319 } n;
320 size_t largest, len;
321
322 largest = end - p;
323 if (largest > 128)
324 largest = 128;
325 memcpy(&n, p, largest);
326
327 len = (n->info & WATCH_INFO_LENGTH) >>
328 WATCH_INFO_LENGTH__SHIFT;
329 if (len == 0 || len > largest)
330 return;
331
332 switch (n.n.type) {
333 case WATCH_TYPE_META:
334 got_meta(&n.n);
335 case WATCH_TYPE_KEY_NOTIFY:
336 saw_key_change(&n.n);
337 break;
338 }
339
340 p += len;
341 }
342 }
343 }
344

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

일반 notification mechanism

1-21

일반 notification mechanism

일반 notification mechanism은 standard pipe driver 위에 구축되며, kernel의 notification message를 userspace가 연 pipe에 실질적으로 splice합니다. 다음 기능과 함께 사용할 수 있습니다.

  • Key 및 keyring notification

Notification buffer는 다음 항목으로 활성화할 수 있습니다.

"General setup"/"General notification queue"
(CONFIG_WATCH_QUEUE)

이 문서는 다음 section으로 구성됩니다.

.. contents:: :local:

개요

22-50

개요

이 facility는 특별한 mode로 연 pipe로 나타납니다. Pipe의 내부 ring buffer는 kernel이 생성한 message를 보관하며, 이 message는 `read()`로 읽습니다.

이러한 pipe에서는 splice와 비슷한 operation을 비활성화합니다. 특정 상황에서 ring에 추가한 항목을 되돌리려다가 notification message와 뒤섞일 수 있기 때문입니다.

Pipe owner는 어떤 source를 그 pipe로 watch할지 kernel에 알려야 합니다. Pipe에 연결된 source만 message를 삽입합니다. Source 하나를 여러 pipe에 bind하여 모든 pipe에 동시에 message를 넣을 수도 있습니다.

관심 없는 특정 source type과 subevent를 무시하도록 pipe에 filter를 설치할 수도 있습니다.

Ring에 빈 slot이 없거나 미리 할당된 message buffer가 없으면 message를 버립니다. 두 경우 모두 현재 buffer의 마지막 message를 읽은 뒤 `read()`가 output buffer에 `WATCH_META_LOSS_NOTIFICATION` message를 삽입합니다.

Kernel은 notification을 생성할 때 consumer가 수집하기를 기다리지 않고 계속 진행합니다. 따라서 spinlock을 잡은 동안에도 notification을 생성할 수 있으며, userspace malfunction이 kernel을 무기한 지연시키는 것도 막습니다.

Message structure

51-93

Message structure

Notification message는 짧은 header로 시작합니다.

struct watch_notification {
        __u32        type:24;
        __u32        subtype:8;
        __u32        info;
};

`type`은 notification record의 source를 나타내고 `subtype`은 그 source에서 온 record type을 나타냅니다. 아래 Watch Sources section을 참조하십시오. Type은 watch queue 자체가 내부적으로 생성하는 특별한 record type인 `WATCH_TYPE_META`일 수도 있습니다. Subtype은 두 가지입니다.

  • `WATCH_META_REMOVAL_NOTIFICATION`
  • `WATCH_META_LOSS_NOTIFICATION`

첫 번째는 watch를 설치한 object가 제거되거나 파괴되었음을 나타내고, 두 번째는 일부 message가 손실되었음을 나타냅니다.

`info`는 다음을 포함한 여러 정보를 나타냅니다.

  • Header를 포함한 message 길이(byte). `WATCH_INFO_LENGTH`로 mask하고 `WATCH_INFO_LENGTH__SHIFT`만큼 shift합니다. Record 크기는 8에서 127 byte 사이입니다.
  • Watch ID. `WATCH_INFO_ID`로 mask하고 `WATCH_INFO_ID__SHIFT`만큼 shift합니다. Watch에 대한 caller ID이며 0에서 255 사이입니다. 여러 watch가 queue 하나를 공유할 때 이를 구분합니다.
  • Type별 field인 `WATCH_INFO_TYPE_INFO`. Notification producer가 type과 subtype에 고유한 의미를 나타내도록 설정합니다.

`info`에서 길이를 제외한 모든 항목을 filtering에 사용할 수 있습니다.

Header 뒤에는 supplementary information이 올 수 있습니다. 그 형식은 type과 subtype이 정의합니다.

Watch list notification source API

94-121

Watch List, 즉 notification source API

Watch list는 notification source를 subscribe한 watcher의 목록입니다. List는 key나 superblock 같은 object에 붙을 수도 있고, device event처럼 global일 수도 있습니다.

Userspace 관점에서 non-global watch list는 보통 자신이 속한 object를 참조해 가리킵니다. 예를 들어 `KEYCTL_NOTIFY`에 특정 key를 watch할 key serial number를 전달합니다.

Watch list를 관리하는 function은 다음과 같습니다.

void init_watch_list(struct watch_list *wlist,
                     void (*release_watch)(struct watch *wlist));

Watch list를 초기화합니다. `release_watch`가 `NULL`이 아니면 watch_list object를 파괴할 때 호출해야 하는 function을 뜻하며, watch list가 watched object에 보유한 reference를 버립니다.

* ``void remove_watch_list(struct watch_list *wlist);``

Watch_list를 subscribe한 모든 watch를 제거하고 free한 뒤 watch_list object 자체를 파괴합니다.

Watch queue notification output API

122-141

Watch Queue, 즉 notification output API

Watch queue는 notification record가 기록될 application 할당 buffer입니다. 동작은 pipe device driver 내부에 완전히 숨겨져 있지만 watch를 설정하려면 reference를 얻어야 합니다. 다음 function으로 관리합니다.

* ``struct watch_queue *get_watch_queue(int fd);``

Watch queue는 buffer를 구현하는 pipe의 fd로 kernel에 지정되므로 userspace는 system call을 통해 그 fd를 전달해야 합니다. 이 function은 system call에서 watch queue의 opaque pointer를 찾는 데 사용할 수 있습니다.

* ``void put_watch_queue(struct watch_queue *wqueue);``

`get_watch_queue()`에서 얻은 reference를 버립니다.

Watch subscription API

142-203

Watch subscription API

Watch는 watch list에 대한 subscription이며 notification record를 기록할 watch queue, 즉 buffer를 지정합니다. Watch queue object는 userspace가 설정한 해당 object용 filtering rule도 보유할 수 있습니다. Watch struct의 일부는 driver가 설정할 수 있습니다.

struct watch {
        union {
                u32                info_id;        /* ID to be OR'd in to info field */
                ...
        };
        void                        *private;        /* Private data for the watched object */
        u64                        id;                /* Internal identifier */
        ...
};

`info_id` 값은 userspace에서 받은 8-bit number를 `WATCH_INFO_ID__SHIFT`만큼 shift한 것이어야 합니다. Notification이 연결된 watch queue buffer에 기록될 때 이 값은 `struct watch_notification::info`의 `WATCH_INFO_ID` field에 OR됩니다.

`private` field는 watch_list와 연결된 driver data이며 `watch_list::release_watch()` method가 정리합니다.

`id` field는 source ID입니다. 다른 ID로 post된 notification은 무시합니다.

Watch를 관리하는 function은 다음과 같습니다.

* ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``

Watch object를 초기화하고 watch queue를 가리키는 pointer를 설정합니다. Lockdep complaint를 피하도록 적절한 barrier를 사용합니다.

* ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``

Watch를 notification source인 watch list에 subscribe합니다. 호출 전에 watch struct에서 driver가 설정할 수 있는 field를 설정해야 합니다.

int remove_watch_from_object(struct watch_list *wlist,
                             struct watch_queue *wqueue,
                             u64 id, false);

지정한 watch queue인 `wqueue`와 object identifier인 `id`가 일치하는 watch를 watch list에서 제거합니다. Watch가 제거되었음을 나타내는 `WATCH_META_REMOVAL_NOTIFICATION`을 watch queue로 보냅니다.

* ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``

Watch list에서 모든 watch를 제거합니다. 파괴 준비 과정에서 호출하며, 이 시점에는 새 watch가 watch list에 접근할 수 없어야 합니다. Subscribe된 각 watch의 watch queue에 `WATCH_META_REMOVAL_NOTIFICATION`을 보내 제거 사실을 알립니다.

Notification posting API

204-227

Notification posting API

Subscribe된 watch가 notification을 볼 수 있도록 watch list에 post하려면 다음 function을 사용합니다.

void post_watch_notification(struct watch_list *wlist,
                             struct watch_notification *n,
                             const struct cred *cred,
                             u64 id);

Notification은 미리 format해야 하며 header pointer인 `n`을 전달합니다. Notification은 header보다 클 수 있고, buffer slot 단위 크기는 `n->info & WATCH_INFO_LENGTH`에 기록됩니다.

`cred` struct는 source, 즉 subject의 credential을 나타냅니다. SELinux 같은 LSM에 전달되어 각 queue의 credential, 즉 object에 따라 개별 queue에 note를 기록하도록 허용하거나 억제합니다.

`id`는 key serial number 같은 source object의 ID입니다. 동일한 ID가 설정된 watch만 이 notification을 받습니다.

Watch source

228-240

Watch source

특정 buffer 하나는 여러 source에서 data를 받을 수 있습니다. Source에는 다음 항목이 있습니다.

  • `WATCH_TYPE_KEY_NOTIFY`

이 type의 notification은 keyring content 또는 key attribute 변경을 포함하여 key와 keyring의 변경을 나타냅니다.

자세한 내용은 `Documentation/security/keys/core.rst`를 참조하십시오.

Event filtering

241-290

Event filtering

Watch queue를 만든 뒤 다음과 같이 filter 집합을 적용하여 수신하는 event를 제한할 수 있습니다.

struct watch_notification_filter filter = {
        ...
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)

Filter description은 다음 type의 variable입니다.

struct watch_notification_filter {
        __u32        nr_filters;
        __u32        __reserved;
        struct watch_notification_type_filter filters[];
};

`nr_filters`는 `filters[]` 안의 filter 수이고 `__reserved`는 0이어야 합니다. `filters` array element의 type은 다음과 같습니다.

struct watch_notification_type_filter {
        __u32        type;
        __u32        info_filter;
        __u32        info_mask;
        __u32        subtype_filter[8];
};

각 field의 의미는 다음과 같습니다.

  • `type`은 filtering할 event type이며 `WATCH_TYPE_KEY_NOTIFY` 같은 값이어야 합니다.
  • `info_filter`와 `info_mask`는 notification record의 info field에 대한 filter로 동작합니다. 아래 조건을 만족할 때만 notification을 buffer에 기록합니다.
  • `subtype_filter`는 관심 있는 subtype을 나타내는 bitmask입니다. `subtype_filter[0]`의 bit 0은 subtype 0, bit 1은 subtype 1에 해당하며 이후도 같은 방식입니다.
(watch.info & info_mask) == info_filter

예를 들어 이 조건을 사용해 mount tree에서 정확히 watched point에 있지 않은 event를 무시할 수 있습니다.

`ioctl()` argument가 `NULL`이면 filter를 제거하며 watched source의 모든 event가 전달됩니다.

Userspace code 예제

291-343

Userspace code 예제

다음과 같은 code로 buffer를 만듭니다.

pipe2(fds, O_TMPFILE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);

그런 다음 keyring change notification을 받도록 설정할 수 있습니다.

keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);

Notification은 다음과 같은 code로 consume할 수 있습니다.

static void consumer(int rfd, struct watch_queue_buffer *buf)
{
        unsigned char buffer[128];
        ssize_t buf_len;

        while (buf_len = read(rfd, buffer, sizeof(buffer)),
               buf_len > 0
               ) {
                void *p = buffer;
                void *end = buffer + buf_len;
                while (p < end) {
                        union {
                                struct watch_notification n;
                                unsigned char buf1[128];
                        } n;
                        size_t largest, len;

                        largest = end - p;
                        if (largest > 128)
                                largest = 128;
                        memcpy(&n, p, largest);

                        len = (n->info & WATCH_INFO_LENGTH) >>
                                WATCH_INFO_LENGTH__SHIFT;
                        if (len == 0 || len > largest)
                                return;

                        switch (n.n.type) {
                        case WATCH_TYPE_META:
                                got_meta(&n.n);
                        case WATCH_TYPE_KEY_NOTIFY:
                                saw_key_change(&n.n);
                                break;
                        }

                        p += len;
                }
        }
}