diff options
| author | Philipp Stephani | 2020-12-30 23:43:07 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2020-12-31 00:18:02 +0100 |
| commit | 8bc85d46cc9214a531f2d2ecb3f5fb48af8105a6 (patch) | |
| tree | 346a7c64ac0b6a78eacdeb69b73a446d33d87266 | |
| parent | f43f1e32e92ef341077b4ffdfdd743b1a6d9d284 (diff) | |
| download | emacs-8bc85d46cc9214a531f2d2ecb3f5fb48af8105a6.tar.gz emacs-8bc85d46cc9214a531f2d2ecb3f5fb48af8105a6.zip | |
Manually limit file descriptors that we select on to FD_SETSIZE.
This works even if another thread or process resets the resource limit
for open file descriptors, e.g., using 'prlimit' on GNU/Linux.
* src/process.c (create_process, create_pty, Fmake_pipe_process)
(Fmake_serial_process, connect_network_socket)
(server_accept_connection): Limit file descriptors to FD_SETSIZE.
* test/src/process-tests.el (process-tests--with-raised-rlimit): New
helper macro.
(process-tests--fd-setsize-test): Rename from
'process-tests--with-many-pipes'. Increase resource limit during test
if possible.
(process-tests/fd-setsize-no-crash/make-process)
(process-tests/fd-setsize-no-crash/make-pipe-process)
(process-tests/fd-setsize-no-crash/make-network-process)
(process-tests--new-pty): Rename callers.
| -rw-r--r-- | src/process.c | 25 | ||||
| -rw-r--r-- | test/src/process-tests.el | 100 |
2 files changed, 96 insertions, 29 deletions
diff --git a/src/process.c b/src/process.c index 7c56366d8e1..ba2bb3c9e46 100644 --- a/src/process.c +++ b/src/process.c | |||
| @@ -2114,6 +2114,9 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) | |||
| 2114 | } | 2114 | } |
| 2115 | } | 2115 | } |
| 2116 | 2116 | ||
| 2117 | if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) | ||
| 2118 | report_file_errno ("Creating pipe", Qnil, EMFILE); | ||
| 2119 | |||
| 2117 | #ifndef WINDOWSNT | 2120 | #ifndef WINDOWSNT |
| 2118 | if (emacs_pipe (p->open_fd + READ_FROM_EXEC_MONITOR) != 0) | 2121 | if (emacs_pipe (p->open_fd + READ_FROM_EXEC_MONITOR) != 0) |
| 2119 | report_file_error ("Creating pipe", Qnil); | 2122 | report_file_error ("Creating pipe", Qnil); |
| @@ -2210,6 +2213,8 @@ create_pty (Lisp_Object process) | |||
| 2210 | if (pty_fd >= 0) | 2213 | if (pty_fd >= 0) |
| 2211 | { | 2214 | { |
| 2212 | p->open_fd[SUBPROCESS_STDIN] = pty_fd; | 2215 | p->open_fd[SUBPROCESS_STDIN] = pty_fd; |
| 2216 | if (FD_SETSIZE <= pty_fd) | ||
| 2217 | report_file_errno ("Opening pty", Qnil, EMFILE); | ||
| 2213 | #if ! defined (USG) || defined (USG_SUBTTY_WORKS) | 2218 | #if ! defined (USG) || defined (USG_SUBTTY_WORKS) |
| 2214 | /* On most USG systems it does not work to open the pty's tty here, | 2219 | /* On most USG systems it does not work to open the pty's tty here, |
| 2215 | then close it and reopen it in the child. */ | 2220 | then close it and reopen it in the child. */ |
| @@ -2317,6 +2322,9 @@ usage: (make-pipe-process &rest ARGS) */) | |||
| 2317 | outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; | 2322 | outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; |
| 2318 | inchannel = p->open_fd[READ_FROM_SUBPROCESS]; | 2323 | inchannel = p->open_fd[READ_FROM_SUBPROCESS]; |
| 2319 | 2324 | ||
| 2325 | if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) | ||
| 2326 | report_file_errno ("Creating pipe", Qnil, EMFILE); | ||
| 2327 | |||
| 2320 | fcntl (inchannel, F_SETFL, O_NONBLOCK); | 2328 | fcntl (inchannel, F_SETFL, O_NONBLOCK); |
| 2321 | fcntl (outchannel, F_SETFL, O_NONBLOCK); | 2329 | fcntl (outchannel, F_SETFL, O_NONBLOCK); |
| 2322 | 2330 | ||
| @@ -3059,6 +3067,8 @@ usage: (make-serial-process &rest ARGS) */) | |||
| 3059 | 3067 | ||
| 3060 | fd = serial_open (port); | 3068 | fd = serial_open (port); |
| 3061 | p->open_fd[SUBPROCESS_STDIN] = fd; | 3069 | p->open_fd[SUBPROCESS_STDIN] = fd; |
| 3070 | if (FD_SETSIZE <= fd) | ||
| 3071 | report_file_errno ("Opening serial port", Qnil, EMFILE); | ||
| 3062 | p->infd = fd; | 3072 | p->infd = fd; |
| 3063 | p->outfd = fd; | 3073 | p->outfd = fd; |
| 3064 | if (fd > max_desc) | 3074 | if (fd > max_desc) |
| @@ -3280,6 +3290,7 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, | |||
| 3280 | if (!NILP (use_external_socket_p)) | 3290 | if (!NILP (use_external_socket_p)) |
| 3281 | { | 3291 | { |
| 3282 | socket_to_use = external_sock_fd; | 3292 | socket_to_use = external_sock_fd; |
| 3293 | eassert (socket_to_use < FD_SETSIZE); | ||
| 3283 | 3294 | ||
| 3284 | /* Ensure we don't consume the external socket twice. */ | 3295 | /* Ensure we don't consume the external socket twice. */ |
| 3285 | external_sock_fd = -1; | 3296 | external_sock_fd = -1; |
| @@ -3321,6 +3332,13 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, | |||
| 3321 | xerrno = errno; | 3332 | xerrno = errno; |
| 3322 | continue; | 3333 | continue; |
| 3323 | } | 3334 | } |
| 3335 | if (FD_SETSIZE <= s) | ||
| 3336 | { | ||
| 3337 | emacs_close (s); | ||
| 3338 | s = -1; | ||
| 3339 | xerrno = EMFILE; | ||
| 3340 | continue; | ||
| 3341 | } | ||
| 3324 | } | 3342 | } |
| 3325 | 3343 | ||
| 3326 | if (p->is_non_blocking_client && ! (SOCK_NONBLOCK && socket_to_use < 0)) | 3344 | if (p->is_non_blocking_client && ! (SOCK_NONBLOCK && socket_to_use < 0)) |
| @@ -4782,6 +4800,13 @@ server_accept_connection (Lisp_Object server, int channel) | |||
| 4782 | 4800 | ||
| 4783 | s = accept4 (channel, &saddr.sa, &len, SOCK_CLOEXEC); | 4801 | s = accept4 (channel, &saddr.sa, &len, SOCK_CLOEXEC); |
| 4784 | 4802 | ||
| 4803 | if (FD_SETSIZE <= s) | ||
| 4804 | { | ||
| 4805 | emacs_close (s); | ||
| 4806 | s = -1; | ||
| 4807 | errno = EMFILE; | ||
| 4808 | } | ||
| 4809 | |||
| 4785 | if (s < 0) | 4810 | if (s < 0) |
| 4786 | { | 4811 | { |
| 4787 | int code = errno; | 4812 | int code = errno; |
diff --git a/test/src/process-tests.el b/test/src/process-tests.el index 590f72f9b03..eee8636067c 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el | |||
| @@ -426,11 +426,52 @@ add some process objects to VAR." | |||
| 426 | ,(macroexp-progn body) | 426 | ,(macroexp-progn body) |
| 427 | (mapc #'delete-process ,var)))) | 427 | (mapc #'delete-process ,var)))) |
| 428 | 428 | ||
| 429 | (defmacro process-tests--with-many-pipes (&rest body) | 429 | (defmacro process-tests--with-raised-rlimit (&rest body) |
| 430 | "Generate lots of pipe processes. | 430 | "Evaluate BODY using a higher limit for the number of open files. |
| 431 | Attempt to set the resource limit for the number of open files | ||
| 432 | temporarily to the highest possible value." | ||
| 433 | (declare (indent 0) (debug t)) | ||
| 434 | (let ((prlimit (make-symbol "prlimit")) | ||
| 435 | (soft (make-symbol "soft")) | ||
| 436 | (hard (make-symbol "hard")) | ||
| 437 | (pid-arg (make-symbol "pid-arg"))) | ||
| 438 | `(let ((,prlimit (executable-find "prlimit")) | ||
| 439 | (,pid-arg (format "--pid=%d" (emacs-pid))) | ||
| 440 | (,soft nil) (,hard nil)) | ||
| 441 | (cl-flet ((set-limit | ||
| 442 | (value) | ||
| 443 | (cl-check-type value natnum) | ||
| 444 | (when ,prlimit | ||
| 445 | (call-process ,prlimit nil nil nil | ||
| 446 | ,pid-arg | ||
| 447 | (format "--nofile=%d:" value))))) | ||
| 448 | (when ,prlimit | ||
| 449 | (with-temp-buffer | ||
| 450 | (when (eql (call-process ,prlimit nil t nil | ||
| 451 | ,pid-arg "--nofile" | ||
| 452 | "--raw" "--noheadings" | ||
| 453 | "--output=SOFT,HARD") | ||
| 454 | 0) | ||
| 455 | (goto-char (point-min)) | ||
| 456 | (when (looking-at (rx (group (+ digit)) (+ blank) | ||
| 457 | (group (+ digit)) ?\n)) | ||
| 458 | (setq ,soft (string-to-number | ||
| 459 | (match-string-no-properties 1)) | ||
| 460 | ,hard (string-to-number | ||
| 461 | (match-string-no-properties 2)))))) | ||
| 462 | (and ,soft ,hard (< ,soft ,hard) | ||
| 463 | (set-limit ,hard))) | ||
| 464 | (unwind-protect | ||
| 465 | ,(macroexp-progn body) | ||
| 466 | (when ,soft (set-limit ,soft))))))) | ||
| 467 | |||
| 468 | (defmacro process-tests--fd-setsize-test (&rest body) | ||
| 469 | "Run BODY as a test for FD_SETSIZE overflow. | ||
| 431 | Try to generate pipe processes until we are close to the | 470 | Try to generate pipe processes until we are close to the |
| 432 | FD_SETSIZE limit. Within BODY, only a small number of file | 471 | FD_SETSIZE limit. Within BODY, only a small number of file |
| 433 | descriptors should still be available." | 472 | descriptors should still be available. Furthermore, raise the |
| 473 | maximum number of open files in the Emacs process above | ||
| 474 | FD_SETSIZE." | ||
| 434 | (declare (indent 0) (debug t)) | 475 | (declare (indent 0) (debug t)) |
| 435 | (let ((process (make-symbol "process")) | 476 | (let ((process (make-symbol "process")) |
| 436 | (processes (make-symbol "processes")) | 477 | (processes (make-symbol "processes")) |
| @@ -440,28 +481,29 @@ descriptors should still be available." | |||
| 440 | ;; MS-Windows we artificially limit FD_SETSIZE to 64, see the | 481 | ;; MS-Windows we artificially limit FD_SETSIZE to 64, see the |
| 441 | ;; commentary in w32proc.c. | 482 | ;; commentary in w32proc.c. |
| 442 | (fd-setsize (if (eq system-type 'windows-nt) 64 1024))) | 483 | (fd-setsize (if (eq system-type 'windows-nt) 64 1024))) |
| 443 | `(process-tests--with-buffers ,buffers | 484 | `(process-tests--with-raised-rlimit |
| 444 | (process-tests--with-processes ,processes | 485 | (process-tests--with-buffers ,buffers |
| 445 | ;; First, allocate enough pipes to definitely exceed the | 486 | (process-tests--with-processes ,processes |
| 446 | ;; FD_SETSIZE limit. | 487 | ;; First, allocate enough pipes to definitely exceed the |
| 447 | (cl-loop for i from 1 to ,(1+ fd-setsize) | 488 | ;; FD_SETSIZE limit. |
| 448 | for ,buffer = (generate-new-buffer | 489 | (cl-loop for i from 1 to ,(1+ fd-setsize) |
| 449 | (format " *pipe %d*" i)) | 490 | for ,buffer = (generate-new-buffer |
| 450 | do (push ,buffer ,buffers) | 491 | (format " *pipe %d*" i)) |
| 451 | for ,process = (process-tests--ignore-EMFILE | 492 | do (push ,buffer ,buffers) |
| 452 | (make-pipe-process | 493 | for ,process = (process-tests--ignore-EMFILE |
| 453 | :name (format "pipe %d" i) | 494 | (make-pipe-process |
| 454 | :buffer ,buffer | 495 | :name (format "pipe %d" i) |
| 455 | :coding 'no-conversion | 496 | :buffer ,buffer |
| 456 | :noquery t)) | 497 | :coding 'no-conversion |
| 457 | while ,process | 498 | :noquery t)) |
| 458 | do (push ,process ,processes)) | 499 | while ,process |
| 459 | (unless (cddr ,processes) | 500 | do (push ,process ,processes)) |
| 460 | (ert-fail "Couldn't allocate enough pipes")) | 501 | (unless (cddr ,processes) |
| 461 | ;; Delete two pipes to test more edge cases. | 502 | (ert-fail "Couldn't allocate enough pipes")) |
| 462 | (delete-process (pop ,processes)) | 503 | ;; Delete two pipes to test more edge cases. |
| 463 | (delete-process (pop ,processes)) | 504 | (delete-process (pop ,processes)) |
| 464 | ,@body)))) | 505 | (delete-process (pop ,processes)) |
| 506 | ,@body))))) | ||
| 465 | 507 | ||
| 466 | (defmacro process-tests--with-temp-file (var &rest body) | 508 | (defmacro process-tests--with-temp-file (var &rest body) |
| 467 | "Bind VAR to the name of a new regular file and evaluate BODY. | 509 | "Bind VAR to the name of a new regular file and evaluate BODY. |
| @@ -502,7 +544,7 @@ FD_SETSIZE file descriptors (Bug#24325)." | |||
| 502 | (skip-unless sleep) | 544 | (skip-unless sleep) |
| 503 | (dolist (conn-type '(pipe pty)) | 545 | (dolist (conn-type '(pipe pty)) |
| 504 | (ert-info ((format "Connection type `%s'" conn-type)) | 546 | (ert-info ((format "Connection type `%s'" conn-type)) |
| 505 | (process-tests--with-many-pipes | 547 | (process-tests--fd-setsize-test |
| 506 | (process-tests--with-processes processes | 548 | (process-tests--with-processes processes |
| 507 | ;; Start processes until we exhaust the file descriptor | 549 | ;; Start processes until we exhaust the file descriptor |
| 508 | ;; set size. We assume that each process requires at | 550 | ;; set size. We assume that each process requires at |
| @@ -538,7 +580,7 @@ FD_SETSIZE file descriptors (Bug#24325)." | |||
| 538 | "Check that Emacs doesn't crash when trying to use more than | 580 | "Check that Emacs doesn't crash when trying to use more than |
| 539 | FD_SETSIZE file descriptors (Bug#24325)." | 581 | FD_SETSIZE file descriptors (Bug#24325)." |
| 540 | (with-timeout (60 (ert-fail "Test timed out")) | 582 | (with-timeout (60 (ert-fail "Test timed out")) |
| 541 | (process-tests--with-many-pipes | 583 | (process-tests--fd-setsize-test |
| 542 | (process-tests--with-buffers buffers | 584 | (process-tests--with-buffers buffers |
| 543 | (process-tests--with-processes processes | 585 | (process-tests--with-processes processes |
| 544 | ;; Start processes until we exhaust the file descriptor set | 586 | ;; Start processes until we exhaust the file descriptor set |
| @@ -580,7 +622,7 @@ FD_SETSIZE file descriptors (Bug#24325)." | |||
| 580 | :coding 'no-conversion | 622 | :coding 'no-conversion |
| 581 | :noquery t))) | 623 | :noquery t))) |
| 582 | (push server processes) | 624 | (push server processes) |
| 583 | (process-tests--with-many-pipes | 625 | (process-tests--fd-setsize-test |
| 584 | ;; Start processes until we exhaust the file descriptor | 626 | ;; Start processes until we exhaust the file descriptor |
| 585 | ;; set size. We assume that each process requires at | 627 | ;; set size. We assume that each process requires at |
| 586 | ;; least one file descriptor. | 628 | ;; least one file descriptor. |
| @@ -627,7 +669,7 @@ FD_SETSIZE file descriptors (Bug#24325)." | |||
| 627 | (should tty-name) | 669 | (should tty-name) |
| 628 | (should (file-exists-p tty-name)) | 670 | (should (file-exists-p tty-name)) |
| 629 | (push tty-name tty-names))) | 671 | (push tty-name tty-names))) |
| 630 | (process-tests--with-many-pipes | 672 | (process-tests--fd-setsize-test |
| 631 | (process-tests--with-processes processes | 673 | (process-tests--with-processes processes |
| 632 | (process-tests--with-buffers buffers | 674 | (process-tests--with-buffers buffers |
| 633 | (dolist (tty-name tty-names) | 675 | (dolist (tty-name tty-names) |