aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2017-03-30 11:08:23 -0700
committerPaul Eggert2017-03-30 11:08:42 -0700
commit6ff870218dd4bc015cc4115ceb2febd8d807e57c (patch)
tree67eaa089c0754194e75eea898e24c4a6bcfbe91a /src
parent1be3330b31f9c0d0d0f7d25641e8b81c807ca616 (diff)
downloademacs-6ff870218dd4bc015cc4115ceb2febd8d807e57c.tar.gz
emacs-6ff870218dd4bc015cc4115ceb2febd8d807e57c.zip
Some inotify cleanup
This catches some problems with integer overflow and races that I noticed in inotify.c after reviewing the changes installed to fix Bug#26126. * src/fns.c, src/lisp.h (equal_no_quit): Now extern. * src/inotify.c (aspect_to_inotifymask): Check for cycles and for improper lists. (make_lispy_mask, lispy_mask_match_p): Remove. All callers changed to use INTEGER_TO_CONS and CONS_TO_INTEGER. (inotifyevent_to_event, add_watch): Don’t assume watch descriptors and cookies fit in fixnums. (add_watch): Use assoc_no_quit, not Fassoc. Avoid integer overflow in (very!) long-running processes where the Emacs watch ID could overflow. Avoid some duplicate code. (find_descriptor): New function. (remove_descriptor): First arg is now the returned value from find_descriptor, rather than the descriptor. This way, the value can be removed without calling Fdelete, which might quit. Wait until the end (when watch_list is consistent) before signaling any errors. (remove_watch, inotify_callback): Use find_descriptor to avoid the need for Fdelete. (inotify_callback): Use simpler tests for ioctl failure. Free temporary buffer if signaled, and put it on the stack if small. Use ssize_t to index through read results, to avoid a cast. (valid_watch_descriptor): New function, with a tighter check. (Finotify_rm_watch, Finotify_valid_p): Use it. (Finotify_valid_p): Use assoc_no_quit and ass_no_quit instead of Fassoc. Do not assume the first assoc succeeds. * test/src/inotify-tests.el (inotify-valid-p-simple): Add inotify-valid-p tests, some of which dump core without the fixes noted above.
Diffstat (limited to 'src')
-rw-r--r--src/fns.c3
-rw-r--r--src/inotify.c250
-rw-r--r--src/lisp.h1
3 files changed, 140 insertions, 114 deletions
diff --git a/src/fns.c b/src/fns.c
index 42e2eecf33e..de7fc1b47fc 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -38,7 +38,6 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
38 38
39static void sort_vector_copy (Lisp_Object, ptrdiff_t, 39static void sort_vector_copy (Lisp_Object, ptrdiff_t,
40 Lisp_Object *restrict, Lisp_Object *restrict); 40 Lisp_Object *restrict, Lisp_Object *restrict);
41static bool equal_no_quit (Lisp_Object, Lisp_Object);
42enum equal_kind { EQUAL_NO_QUIT, EQUAL_PLAIN, EQUAL_INCLUDING_PROPERTIES }; 41enum equal_kind { EQUAL_NO_QUIT, EQUAL_PLAIN, EQUAL_INCLUDING_PROPERTIES };
43static bool internal_equal (Lisp_Object, Lisp_Object, 42static bool internal_equal (Lisp_Object, Lisp_Object,
44 enum equal_kind, int, Lisp_Object); 43 enum equal_kind, int, Lisp_Object);
@@ -2121,7 +2120,7 @@ of strings. (`equal' ignores text properties.) */)
2121 Use this only on arguments that are cycle-free and not too large and 2120 Use this only on arguments that are cycle-free and not too large and
2122 are not window configurations. */ 2121 are not window configurations. */
2123 2122
2124static bool 2123bool
2125equal_no_quit (Lisp_Object o1, Lisp_Object o2) 2124equal_no_quit (Lisp_Object o1, Lisp_Object o2)
2126{ 2125{
2127 return internal_equal (o1, o2, EQUAL_NO_QUIT, 0, Qnil); 2126 return internal_equal (o1, o2, EQUAL_NO_QUIT, 0, Qnil);
diff --git a/src/inotify.c b/src/inotify.c
index 004689bd4b5..a0a89aa0f41 100644
--- a/src/inotify.c
+++ b/src/inotify.c
@@ -41,16 +41,16 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
41#ifndef IN_ONLYDIR 41#ifndef IN_ONLYDIR
42# define IN_ONLYDIR 0 42# define IN_ONLYDIR 0
43#endif 43#endif
44#define INOTIFY_DEFAULT_MASK (IN_ALL_EVENTS|IN_EXCL_UNLINK) 44#define INOTIFY_DEFAULT_MASK (IN_ALL_EVENTS | IN_EXCL_UNLINK)
45 45
46/* File handle for inotify. */ 46/* File handle for inotify. */
47static int inotifyfd = -1; 47static int inotifyfd = -1;
48 48
49/* Alist of files being watched. We want the returned descriptor to 49/* Alist of files being watched. We want the returned descriptor to
50 be unique for every watch, but inotify returns the same descriptor 50 be unique for every watch, but inotify returns the same descriptor
51 for multiple calls to inotify_add_watch with the same file. In 51 WD for multiple calls to inotify_add_watch with the same file.
52 order to solve this problem, we add a ID, uniquely identifying a 52 Supply a nonnegative integer ID, so that WD and ID together
53 watch/file combination. 53 uniquely identify a watch/file combination.
54 54
55 For the same reason, we also need to store the watch's mask and we 55 For the same reason, we also need to store the watch's mask and we
56 can't allow the following flags to be used. 56 can't allow the following flags to be used.
@@ -60,12 +60,21 @@ static int inotifyfd = -1;
60 IN_ONESHOT 60 IN_ONESHOT
61 IN_ONLYDIR 61 IN_ONLYDIR
62 62
63 Format: (descriptor . ((id filename callback mask) ...)) 63 Each element of this list is of the form (DESCRIPTOR . WATCHES)
64*/ 64 where no two DESCRIPTOR values are the same. DESCRIPTOR represents
65 the inotify watch descriptor and WATCHES is a list with elements of
66 the form (ID FILENAME CALLBACK MASK), where ID is the integer
67 described above, FILENAME names the file being watched, CALLBACK is
68 invoked when the event occurs, and MASK represents the aspects
69 being watched. The WATCHES list is sorted by ID. Although
70 DESCRIPTOR and MASK are ordinarily integers, they are conses when
71 representing integers outside of fixnum range. */
72
65static Lisp_Object watch_list; 73static Lisp_Object watch_list;
66 74
67static Lisp_Object 75static Lisp_Object
68mask_to_aspects (uint32_t mask) { 76mask_to_aspects (uint32_t mask)
77{
69 Lisp_Object aspects = Qnil; 78 Lisp_Object aspects = Qnil;
70 if (mask & IN_ACCESS) 79 if (mask & IN_ACCESS)
71 aspects = Fcons (Qaccess, aspects); 80 aspects = Fcons (Qaccess, aspects);
@@ -149,15 +158,13 @@ symbol_to_inotifymask (Lisp_Object symb)
149static uint32_t 158static uint32_t
150aspect_to_inotifymask (Lisp_Object aspect) 159aspect_to_inotifymask (Lisp_Object aspect)
151{ 160{
152 if (CONSP (aspect)) 161 if (CONSP (aspect) || NILP (aspect))
153 { 162 {
154 Lisp_Object x = aspect; 163 Lisp_Object x = aspect;
155 uint32_t mask = 0; 164 uint32_t mask = 0;
156 while (CONSP (x)) 165 FOR_EACH_TAIL (x)
157 { 166 mask |= symbol_to_inotifymask (XCAR (x));
158 mask |= symbol_to_inotifymask (XCAR (x)); 167 CHECK_LIST_END (x, aspect);
159 x = XCDR (x);
160 }
161 return mask; 168 return mask;
162 } 169 }
163 else 170 else
@@ -165,25 +172,13 @@ aspect_to_inotifymask (Lisp_Object aspect)
165} 172}
166 173
167static Lisp_Object 174static Lisp_Object
168make_lispy_mask (uint32_t mask)
169{
170 return Fcons (make_number (mask & 0xffff),
171 make_number (mask >> 16));
172}
173
174static bool
175lispy_mask_match_p (Lisp_Object mask, uint32_t other)
176{
177 return (XINT (XCAR (mask)) & other)
178 || ((XINT (XCDR (mask)) << 16) & other);
179}
180
181static Lisp_Object
182inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev) 175inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev)
183{ 176{
184 Lisp_Object name = Qnil; 177 Lisp_Object name;
178 uint32_t mask;
179 CONS_TO_INTEGER (Fnth (make_number (3), watch), uint32_t, mask);
185 180
186 if (! lispy_mask_match_p (Fnth (make_number (3), watch), ev->mask)) 181 if (! (mask & ev->mask))
187 return Qnil; 182 return Qnil;
188 183
189 if (ev->len > 0) 184 if (ev->len > 0)
@@ -195,10 +190,10 @@ inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev)
195 else 190 else
196 name = XCAR (XCDR (watch)); 191 name = XCAR (XCDR (watch));
197 192
198 return list2 (list4 (Fcons (make_number (ev->wd), XCAR (watch)), 193 return list2 (list4 (Fcons (INTEGER_TO_CONS (ev->wd), XCAR (watch)),
199 mask_to_aspects (ev->mask), 194 mask_to_aspects (ev->mask),
200 name, 195 name,
201 make_number (ev->cookie)), 196 INTEGER_TO_CONS (ev->cookie)),
202 Fnth (make_number (2), watch)); 197 Fnth (make_number (2), watch));
203} 198}
204 199
@@ -209,55 +204,88 @@ static Lisp_Object
209add_watch (int wd, Lisp_Object filename, 204add_watch (int wd, Lisp_Object filename,
210 Lisp_Object aspect, Lisp_Object callback) 205 Lisp_Object aspect, Lisp_Object callback)
211{ 206{
212 Lisp_Object descriptor = make_number (wd); 207 Lisp_Object descriptor = INTEGER_TO_CONS (wd);
213 Lisp_Object elt = Fassoc (descriptor, watch_list); 208 Lisp_Object tail = assoc_no_quit (descriptor, watch_list);
214 Lisp_Object watches = Fcdr (elt);
215 Lisp_Object watch, watch_id; 209 Lisp_Object watch, watch_id;
216 Lisp_Object mask = make_lispy_mask (aspect_to_inotifymask (aspect)); 210 uint32_t imask = aspect_to_inotifymask (aspect);
211 Lisp_Object mask = INTEGER_TO_CONS (imask);
217 212
218 int id = 0; 213 EMACS_INT id = 0;
219 214 if (NILP (tail))
220 while (! NILP (watches)) 215 {
216 tail = list1 (descriptor);
217 watch_list = Fcons (tail, watch_list);
218 }
219 else
221 { 220 {
222 id = max (id, 1 + XINT (XCAR (XCAR (watches)))); 221 /* Assign a watch ID that is not already in use, by looking
223 watches = XCDR (watches); 222 for a gap in the existing sorted list. */
223 for (; ! NILP (XCDR (tail)); tail = XCDR (tail), id++)
224 if (!EQ (XCAR (XCAR (XCDR (tail))), make_number (id)))
225 break;
226 if (MOST_POSITIVE_FIXNUM < id)
227 emacs_abort ();
224 } 228 }
225 229
226 watch_id = make_number (id); 230 watch_id = make_number (id);
227 watch = list4 (watch_id, filename, callback, mask); 231 watch = list4 (watch_id, filename, callback, mask);
228 232 XSETCDR (tail, Fcons (watch, XCDR (tail)));
229 if (NILP (elt))
230 watch_list = Fcons (Fcons (descriptor, Fcons (watch, Qnil)),
231 watch_list);
232 else
233 XSETCDR (elt, Fcons (watch, XCDR (elt)));
234 233
235 return Fcons (descriptor, watch_id); 234 return Fcons (descriptor, watch_id);
236} 235}
237 236
238/* Remove all watches associated with descriptor. If INVALID_P is 237/* Find the watch list element (if any) matching DESCRIPTOR. Return
239 true, the descriptor is already invalid, i.e. it received a 238 nil if not found. If found, return t if the first element matches
240 IN_IGNORED event. In this case skip calling inotify_rm_watch. */ 239 DESCRIPTOR; otherwise, return the cons whose cdr matches
240 DESCRIPTOR. This lets the caller easily remove the element
241 matching DESCRIPTOR without having to search for it again, and
242 without calling Fdelete (which might quit). */
243
244static Lisp_Object
245find_descriptor (Lisp_Object descriptor)
246{
247 Lisp_Object tail, prevtail = Qt;
248 for (tail = watch_list; !NILP (tail); prevtail = tail, tail = XCDR (tail))
249 if (equal_no_quit (XCAR (XCAR (tail)), descriptor))
250 return prevtail;
251 return Qnil;
252}
253
254/* Remove all watches associated with the watch list element after
255 PREVTAIL, or after the first element if PREVTAIL is t. If INVALID_P
256 is true, the descriptor is already invalid, i.e., it received a
257 IN_IGNORED event. In this case skip calling inotify_rm_watch. */
241static void 258static void
242remove_descriptor (Lisp_Object descriptor, bool invalid_p) 259remove_descriptor (Lisp_Object prevtail, bool invalid_p)
243{ 260{
244 Lisp_Object elt = Fassoc (descriptor, watch_list); 261 Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
245 262
246 if (! NILP (elt)) 263 int inotify_errno = 0;
264 if (! invalid_p)
247 { 265 {
248 int wd = XINT (descriptor); 266 int wd;
267 CONS_TO_INTEGER (XCAR (XCAR (tail)), int, wd);
268 if (inotify_rm_watch (inotifyfd, wd) != 0)
269 inotify_errno = errno;
270 }
249 271
250 watch_list = Fdelete (elt, watch_list); 272 if (CONSP (prevtail))
251 if (! invalid_p) 273 XSETCDR (prevtail, XCDR (tail));
252 if (inotify_rm_watch (inotifyfd, wd) == -1) 274 else
253 report_file_notify_error ("Could not rm watch", descriptor); 275 {
276 watch_list = XCDR (tail);
277 if (NILP (watch_list))
278 {
279 delete_read_fd (inotifyfd);
280 emacs_close (inotifyfd);
281 inotifyfd = -1;
282 }
254 } 283 }
255 /* Cleanup if no more files are watched. */ 284
256 if (NILP (watch_list)) 285 if (inotify_errno != 0)
257 { 286 {
258 emacs_close (inotifyfd); 287 errno = inotify_errno;
259 delete_read_fd (inotifyfd); 288 report_file_notify_error ("Could not rm watch", XCAR (tail));
260 inotifyfd = -1;
261 } 289 }
262} 290}
263 291
@@ -265,19 +293,19 @@ remove_descriptor (Lisp_Object descriptor, bool invalid_p)
265static void 293static void
266remove_watch (Lisp_Object descriptor, Lisp_Object id) 294remove_watch (Lisp_Object descriptor, Lisp_Object id)
267{ 295{
268 Lisp_Object elt = Fassoc (descriptor, watch_list); 296 Lisp_Object prevtail = find_descriptor (descriptor);
269 297 if (NILP (prevtail))
270 if (! NILP (elt)) 298 return;
271 { 299
272 Lisp_Object watch = Fassoc (id, XCDR (elt)); 300 Lisp_Object elt = XCAR (CONSP (prevtail) ? XCDR (prevtail) : watch_list);
273 301 for (Lisp_Object prev = elt; !NILP (XCDR (prev)); prev = XCDR (prev))
274 if (! NILP (watch)) 302 if (EQ (id, XCAR (XCAR (XCDR (prev)))))
275 XSETCDR (elt, Fdelete (watch, XCDR (elt))); 303 {
276 304 XSETCDR (prev, XCDR (XCDR (prev)));
277 /* Remove the descriptor if noone is watching it. */ 305 if (NILP (XCDR (elt)))
278 if (NILP (XCDR (elt))) 306 remove_descriptor (prevtail, false);
279 remove_descriptor (descriptor, false); 307 break;
280 } 308 }
281} 309}
282 310
283/* This callback is called when the FD is available for read. The inotify 311/* This callback is called when the FD is available for read. The inotify
@@ -285,52 +313,44 @@ remove_watch (Lisp_Object descriptor, Lisp_Object id)
285static void 313static void
286inotify_callback (int fd, void *_) 314inotify_callback (int fd, void *_)
287{ 315{
288 struct input_event event;
289 int to_read; 316 int to_read;
290 char *buffer; 317 if (ioctl (fd, FIONREAD, &to_read) < 0)
291 ssize_t n;
292 size_t i;
293
294 to_read = 0;
295 if (ioctl (fd, FIONREAD, &to_read) == -1)
296 report_file_notify_error ("Error while retrieving file system events", 318 report_file_notify_error ("Error while retrieving file system events",
297 Qnil); 319 Qnil);
298 buffer = xmalloc (to_read); 320 USE_SAFE_ALLOCA;
299 n = read (fd, buffer, to_read); 321 char *buffer = SAFE_ALLOCA (to_read);
322 ssize_t n = read (fd, buffer, to_read);
300 if (n < 0) 323 if (n < 0)
301 { 324 report_file_notify_error ("Error while reading file system events", Qnil);
302 xfree (buffer);
303 report_file_notify_error ("Error while reading file system events", Qnil);
304 }
305 325
326 struct input_event event;
306 EVENT_INIT (event); 327 EVENT_INIT (event);
307 event.kind = FILE_NOTIFY_EVENT; 328 event.kind = FILE_NOTIFY_EVENT;
308 329
309 i = 0; 330 for (ssize_t i = 0; i < n; )
310 while (i < (size_t)n)
311 { 331 {
312 struct inotify_event *ev = (struct inotify_event *) &buffer[i]; 332 struct inotify_event *ev = (struct inotify_event *) &buffer[i];
313 Lisp_Object descriptor = make_number (ev->wd); 333 Lisp_Object descriptor = INTEGER_TO_CONS (ev->wd);
314 Lisp_Object elt = Fassoc (descriptor, watch_list); 334 Lisp_Object prevtail = find_descriptor (descriptor);
315 335
316 if (! NILP (elt)) 336 if (! NILP (prevtail))
317 { 337 {
318 Lisp_Object watches = XCDR (elt); 338 Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
319 while (! NILP (watches)) 339 for (Lisp_Object watches = XCDR (XCAR (tail)); ! NILP (watches);
340 watches = XCDR (watches))
320 { 341 {
321 event.arg = inotifyevent_to_event (XCAR (watches), ev); 342 event.arg = inotifyevent_to_event (XCAR (watches), ev);
322 if (!NILP (event.arg)) 343 if (!NILP (event.arg))
323 kbd_buffer_store_event (&event); 344 kbd_buffer_store_event (&event);
324 watches = XCDR (watches);
325 } 345 }
326 /* If event was removed automatically: Drop it from watch list. */ 346 /* If event was removed automatically: Drop it from watch list. */
327 if (ev->mask & IN_IGNORED) 347 if (ev->mask & IN_IGNORED)
328 remove_descriptor (descriptor, true); 348 remove_descriptor (prevtail, true);
329 } 349 }
330 i += sizeof (*ev) + ev->len; 350 i += sizeof (*ev) + ev->len;
331 } 351 }
332 352
333 xfree (buffer); 353 SAFE_FREE ();
334} 354}
335 355
336DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0, 356DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
@@ -407,7 +427,7 @@ IN_ONLYDIR */)
407 427
408 if (inotifyfd < 0) 428 if (inotifyfd < 0)
409 { 429 {
410 inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC); 430 inotifyfd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
411 if (inotifyfd < 0) 431 if (inotifyfd < 0)
412 report_file_notify_error ("File watching is not available", Qnil); 432 report_file_notify_error ("File watching is not available", Qnil);
413 watch_list = Qnil; 433 watch_list = Qnil;
@@ -416,12 +436,24 @@ IN_ONLYDIR */)
416 436
417 encoded_file_name = ENCODE_FILE (filename); 437 encoded_file_name = ENCODE_FILE (filename);
418 wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask); 438 wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
419 if (wd == -1) 439 if (wd < 0)
420 report_file_notify_error ("Could not add watch for file", filename); 440 report_file_notify_error ("Could not add watch for file", filename);
421 441
422 return add_watch (wd, filename, aspect, callback); 442 return add_watch (wd, filename, aspect, callback);
423} 443}
424 444
445static bool
446valid_watch_descriptor (Lisp_Object wd)
447{
448 return (CONSP (wd)
449 && (RANGED_INTEGERP (0, XCAR (wd), INT_MAX)
450 || (CONSP (XCAR (wd))
451 && RANGED_INTEGERP ((MOST_POSITIVE_FIXNUM >> 16) + 1,
452 XCAR (XCAR (wd)), INT_MAX >> 16)
453 && RANGED_INTEGERP (0, XCDR (XCAR (wd)), (1 << 16) - 1)))
454 && NATNUMP (XCDR (wd)));
455}
456
425DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0, 457DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
426 doc: /* Remove an existing WATCH-DESCRIPTOR. 458 doc: /* Remove an existing WATCH-DESCRIPTOR.
427 459
@@ -433,9 +465,7 @@ See inotify_rm_watch(2) for more information. */)
433 465
434 Lisp_Object descriptor, id; 466 Lisp_Object descriptor, id;
435 467
436 if (! (CONSP (watch_descriptor) 468 if (! valid_watch_descriptor (watch_descriptor))
437 && INTEGERP (XCAR (watch_descriptor))
438 && INTEGERP (XCDR (watch_descriptor))))
439 report_file_notify_error ("Invalid descriptor ", watch_descriptor); 469 report_file_notify_error ("Invalid descriptor ", watch_descriptor);
440 470
441 descriptor = XCAR (watch_descriptor); 471 descriptor = XCAR (watch_descriptor);
@@ -456,16 +486,12 @@ reason. Removing the watch by calling `inotify-rm-watch' also makes
456it invalid. */) 486it invalid. */)
457 (Lisp_Object watch_descriptor) 487 (Lisp_Object watch_descriptor)
458{ 488{
459 Lisp_Object elt, watch; 489 if (! valid_watch_descriptor (watch_descriptor))
460
461 if (! (CONSP (watch_descriptor)
462 && INTEGERP (XCAR (watch_descriptor))
463 && INTEGERP (XCDR (watch_descriptor))))
464 return Qnil; 490 return Qnil;
465 491 Lisp_Object tail = assoc_no_quit (XCAR (watch_descriptor), watch_list);
466 elt = Fassoc (XCAR (watch_descriptor), watch_list); 492 if (NILP (tail))
467 watch = Fassoc (XCDR (watch_descriptor), XCDR (elt)); 493 return Qnil;
468 494 Lisp_Object watch = assq_no_quit (XCDR (watch_descriptor), XCDR (tail));
469 return ! NILP (watch) ? Qt : Qnil; 495 return ! NILP (watch) ? Qt : Qnil;
470} 496}
471 497
diff --git a/src/lisp.h b/src/lisp.h
index 4b9cd3c4702..3125bd2a5dd 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3376,6 +3376,7 @@ extern Lisp_Object merge (Lisp_Object, Lisp_Object, Lisp_Object);
3376extern Lisp_Object do_yes_or_no_p (Lisp_Object); 3376extern Lisp_Object do_yes_or_no_p (Lisp_Object);
3377extern Lisp_Object concat2 (Lisp_Object, Lisp_Object); 3377extern Lisp_Object concat2 (Lisp_Object, Lisp_Object);
3378extern Lisp_Object concat3 (Lisp_Object, Lisp_Object, Lisp_Object); 3378extern Lisp_Object concat3 (Lisp_Object, Lisp_Object, Lisp_Object);
3379extern bool equal_no_quit (Lisp_Object, Lisp_Object);
3379extern Lisp_Object nconc2 (Lisp_Object, Lisp_Object); 3380extern Lisp_Object nconc2 (Lisp_Object, Lisp_Object);
3380extern Lisp_Object assq_no_quit (Lisp_Object, Lisp_Object); 3381extern Lisp_Object assq_no_quit (Lisp_Object, Lisp_Object);
3381extern Lisp_Object assoc_no_quit (Lisp_Object, Lisp_Object); 3382extern Lisp_Object assoc_no_quit (Lisp_Object, Lisp_Object);