Что делает 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).

Почему вы там выводите INT64_MAX?

Ulrich Eckhardt 30.08.2024 07:50

Действительно странно! Я могу воспроизвести его (M3 MacBook Pro под управлением macOS Sonoma 14..61). После запуска программы echo $?, или ls, или ps не выдает никаких результатов — и lldb кажется зависающим. В итоге мне пришлось вручную закрыть окно. Измените смещение на 0 или 100000000, и (измененная) программа сообщит об успехе, не блокируя все действия. man lseek предусматривает, что ESPIPE («Незаконный поиск») будет возвращено, если дескриптор файла связан с каналом, сокетом или FIFO. Ни man -s 4 tty, ни man -s 4 pty не дают мне никакого освещения.

Jonathan Leffler 30.08.2024 07:50

Кстати, если вы сломаете stdout, вы больше не увидите вывода printf(). Для отладки я бы записал такой вывод в файл и запустил на нем tail -f во втором терминале.

Ulrich Eckhardt 30.08.2024 07:51

Чтобы прояснить поведение Linux, использование lseek на терминальном устройстве хорошо задокументировано здесь: «В Linux использование lseek() на терминальном устройстве возвращает ESPIPE».

mediocrevegetable1 30.08.2024 07:52

Попробовал это на Win10 с MinGW 13.2.0, результат оказался таким же, как и ваш эксперимент с Linux.

the busybee 30.08.2024 07:53

В POSIX говорится: поведение lseek() на устройствах, которые не способны осуществлять поиск, определяется реализацией. Значение смещения файла, связанного с таким устройством, не определено.

Jean-Baptiste Yunès 30.08.2024 10:00

Страница руководства MacOS для lseek выглядит так: Developer.apple.com/library/archive/documentation/System/… (хотя там написано iPhoneOS). Там говорится только: «Некоторые устройства не способны осуществлять поиск. Значение указателя, связанного с таким устройством, не определено». Я думаю, это можно понимать как указание на то, что поведение в MacOS не определено.

John Bollinger 30.08.2024 17:09

Поскольку MacOS является производной BSD, возможно, стоит подумать о том, что на странице руководства BSD говорится об этом (в разделе «Ошибки»): «Если системный вызов lseek() работает на устройстве, которое неспособно искать [другие чем канал, сокет или FIFO], он запросит операцию поиска и вернет ее успешно, даже если поиск не выполнялся. Поскольку аргумент смещения будет безусловно сохранен в файловом дескрипторе этого устройства, нет способа подтвердить, был ли он выполнен. операция поиска удалась или нет [...]».

John Bollinger 30.08.2024 17:13

@JohnBollinger Меня озадачила эта справочная страница MacOS. Может ли поведение, определяемое реализацией, быть неопределенным? Неприятные последствия для TTY кажутся системной ошибкой. Что, если это было сделано на системной консоли в однопользовательском режиме (если такое существует в MacOS)?

Ian Abbott 30.08.2024 17:52

@IanAbbott, я не думаю, что реализация не может определить поведение, определенное реализацией, явно или неявно. Возможно, есть какое-то другое место, где MacOS определяет это конкретное поведение, но я думаю, что более вероятно, что оно просто не соответствует этому вопросу.

John Bollinger 30.08.2024 17:56

@IanAbbott Поскольку эмуляторы терминала и сеансы SSH используют ptys, а консоль использует какое-то реальное терминальное устройство, я подозреваю, что в этих двух случаях поведение совершенно разное.

Barmar 30.08.2024 18:34

Поведение Linux и Windows является ожидаемым результатом. Я согласен, что это ошибка в MacOS.

Barmar 30.08.2024 18:36

Это происходит только с INT64_MAX или с любым значением смещения?

Barmar 30.08.2024 18:36

@Barmar Возможно, вы правы насчет этих двух случаев, но в Linux файловые операции для всех типов TTY проходят через общий уровень подсистемы TTY.

Ian Abbott 31.08.2024 11:38

@IanAbbott Поведение одинаково для всех tty, как в Linux, так и в macOS. В Linux: github.com/torvalds/linux/blob/v6.10/drivers/tty/tty_io.c#L4‌​65 жестких кодов llseek для всех tty к no_llseek, отключив поиск. Драйвер tty предоставляет struct tty_operations, членами которого являются функции для выполнения определенных операций, но поиск не входит в их число. И наоборот, EFBIG, когда write_offset == INT64_MAX в macOS 11+ происходит в коде VFS, поэтому применяется ко всем файлам/устройствам и никоим образом не зависит от tty.

Simon Kissane 31.08.2024 11:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
15
183
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

Я подозреваю, что это было задумано как проверка здравомыслия: «Здесь никогда не должно быть». Не похоже, что есть веская причина искать так далеко в терминале (или в любом реальном файле).

Barmar 31.08.2024 17:48

Другие вопросы по теме