Что делает lseek of tty на macOS?

Рассмотрим следующую программу на языке C, названную weird.c (поскольку то, что она делает, не имеет смысла):

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

int main(void) {
    off_t result = lseek(STDOUT_FILENO, (off_t)INT64_MAX, SEEK_SET);
    if (result != (off_t)-1)
        printf("Seek successful to position %" PRId64 "\n", (int64_t)INT64_MAX);
    else
        perror("lseek failed");
    return 0;
}

Скомпилируйте и запустите его из окна терминала, например. gcc -o weird weird.c && ./weird. Важно, чтобы вы запускали его с терминала (окна терминала, сеанса SSH и т. д.), поэтому stdout — это терминал.

В Linux это завершится с ошибкой lseek failed: Illegal seek — что имеет смысл, поиск по tty не имеет особого смысла.

Однако в macOS это ведет себя по-другому — программа завершает работу, ничего не печатая, а затем — если вы используете bash в качестве оболочки, оболочка также завершает работу. (Напротив, если вы используете zsh в качестве оболочки, приглашение оболочки по-прежнему работает, но никакие команды не отображают никаких результатов.) Я могу только предположить, что это связано с тем, что tty был переведен в какое-то недопустимое состояние. Но что здесь происходит на самом деле?

Я предполагал, что macOS будет вести себя аналогично FreeBSD. Но FreeBSD 14.1-RELEASE (amd64) снова ведет себя по-другому: она печатает:

Seek successful to position 9223372036854775807

Это более понятно, чем то, что делает macOS (рассматривая бессмысленную операцию как недействующую, вместо того, чтобы выдавать ошибку, как это делает Linux).

🤔 А знаете ли вы, что...
C является компилируемым языком, что означает, что программа на C должна быть скомпилирована перед выполнением.


4
183
1

Ответ:

Решено

Пользователь jepler на Hacker News указал на объяснение.

Обратите внимание на этот код в функции vn_write в bsd/vfs/vfs_vnops.c:

        if (write_offset == INT64_MAX) {
                /* writes are not possible */
                error = EFBIG;
                goto error_out;
        }

В основном происходит следующее:

  1. macOS, в отличие от Linux, позволяет использовать lseek для установки смещения терминала, хотя (обычно) смещение на самом деле ничего не делает.
  2. Однако, как только смещение достигнет INT64_MAX, дальнейшие попытки записи завершатся неудачей с EFBIG, если только вы не вернетесь куда-то раньше.

Причина выхода bash заключается в том, что большинство оболочек завершают работу, когда они не могут выполнить запись в stdout/stderr. Что касается того, почему zsh этого не делает, я не уверен, но предполагаю, что код редактирования строки zsh открывает терминальное устройство напрямую и, следовательно, обращается к нему с независимой позицией файла.

Если вы измените мою тестовую программу, чтобы, например, INT64_MAX-100, вы сможете напечатать 100 байт вывода, прежде чем весь вывод начнет давать сбой.

Этот код, похоже, был представлен в xnu-7195.50.7.100.1 (macOS 11 Big Sur) — его не было в xnu-6153.11.26 (macOS 10.15 Catalina).

FreeBSD также позволяет вам lseek выполнять произвольные смещения в терминале, но, в отличие от macOS 11+, не отказывает в дальнейших операциях записи, если вы ищете до INT64_MAX.