LINUX.ORG.RU

Как синхронизировать файловые потоки?

 


0

1

Можно ли в Linux узнать, в какой момент сообщение было записано в файловый поток? Одна программа пишет в два разных потока, другая программа читает из этих двух потоков и должна вывести сообщения в порядке записи (а не в том, каком там накешировалось).

Вот первая программа:

$ clang-format main2.c
#include <stdio.h>

int main() {
  setbuf(stdout, NULL); // Отключение буферизации stdout
  setbuf(stderr, NULL); // Отключение буферизации stderr

  printf("Это stdout\n");
  fflush(stdout);
  fprintf(stderr, "Это stderr\n");
  fflush(stderr);
  fprintf(stderr, "Это stderr2\n");
  fflush(stderr);

  printf("Это stdout 2\n");
  fflush(stdout);
  fprintf(stderr, "Это stderr3\n");
  fflush(stderr);

  printf("Это stdout 3\n");
  fflush(stdout);
  printf("Это stdout 4\n");
  fflush(stdout);

  return 0;
}

Вот вторая программа:

$ clang-format main1.c
#include <errno.h>
#include <poll.h> // Для структуры pollfd и константы POLLIN
#include <pty.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>

#include <time.h> // Для функции nanosleep
int nanosleep(const struct timespec *req, struct timespec *rem);

int max(int a, int b) { return (a > b) ? a : b; }

int main() {
  int master_fd_stdin, slave_fd_stdin;
  int master_fd_stdout, slave_fd_stdout;
  int master_fd_stderr, slave_fd_stderr;

  openpty(&master_fd_stdin, &slave_fd_stdin, NULL, NULL, NULL);
  openpty(&master_fd_stdout, &slave_fd_stdout, NULL, NULL, NULL);
  openpty(&master_fd_stderr, &slave_fd_stderr, NULL, NULL, NULL);

  pid_t pid = fork();
  if (pid == 0) {
    // В дочернем процессе
    close(master_fd_stdin);
    close(master_fd_stdout);
    close(master_fd_stderr);

    dup2(slave_fd_stdin, 0);
    dup2(slave_fd_stdout, 1);
    dup2(slave_fd_stderr, 2);

    close(slave_fd_stdin);
    close(slave_fd_stdout);
    close(slave_fd_stderr);

    execl("./main2", "main2", NULL);
  } else {
    // В родительском процессе
    close(slave_fd_stdin);
    close(slave_fd_stdout);
    close(slave_fd_stderr);

    char buffer[1024];
    ssize_t bytes_read;
    bool is_stdout_expected = true;
    bool is_stderr_expected = true;

    fd_set read_fds;
    int max_fd = max(master_fd_stdout, master_fd_stderr);

    struct pollfd fds[2];
    fds[0].fd = master_fd_stdout;
    fds[0].events = POLLIN;
    fds[1].fd = master_fd_stderr;
    fds[1].events = POLLIN;

    while (is_stderr_expected || is_stdout_expected) {
      FD_ZERO(&read_fds);
      if (is_stdout_expected) {
        FD_SET(master_fd_stdout, &read_fds);
      }
      if (is_stderr_expected) {
        FD_SET(master_fd_stderr, &read_fds);
      }

      select(max_fd + 1, &read_fds, NULL, NULL, NULL);

      if (FD_ISSET(master_fd_stdout, &read_fds)) {
        bytes_read = read(master_fd_stdout, buffer, 1024);
        if (bytes_read > 0) {
          buffer[bytes_read] = '\0'; // Добавляем нулевой символ в конец строки
          printf("\033[0;37m%s\033[0m", buffer);
        } else {
          is_stdout_expected = false;
        }
      }

      if (FD_ISSET(master_fd_stderr, &read_fds)) {
        bytes_read = read(master_fd_stderr, buffer, 1024);
        if (bytes_read > 0) {
          buffer[bytes_read] = '\0'; // Добавляем нулевой символ в конец строки
          printf("\033[0;31m%s\033[0m", buffer);
        } else {
          is_stderr_expected = false;
        }
      }
    }
  }

  return 0;
}

При выводе строки выводятся в неправильном порядке. Как сделать, чтобы выводились в правильном?

Пока у меня мысль такая:

  1. нужно сделать библиотеку, которая содержит функции printf, fprintf и что там ещё бывает. Делать она ничего не должна, должна загрузить системную и все вызовы туда пересылать, кроме особоинтересных.
  2. загрузить эту библиотеку в дочерний процесс вместо системной
  3. каждый факт вывода оборачивать в «пакет», к которому добавлять момент времени (и номер процесса/нити?).
★★★★

Последнее исправление: Shushundr (всего исправлений: 4)

Никак не синхронизировать. Оборачивай в пакеты, да, наверно это единственный вариант костыля. И можно тогда всё слать в один поток а не в разные, а логический номер потока в том же пакете указывать.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Оборачивай в пакеты

Я не умею. Использовать LD_PRELOAD нельзя, потому что «some programs are statically linking the C library, so LD_PRELOAD won’t do anything, because it’s only called when a program is dynamically linked and uses ld to load in function from the libc.so file on your system »

Shushundr ★★★★
() автор топика
Ответ на: комментарий от Shushundr

Это теория или у тебя реально такие программы создают проблемы? Или проблемы вообще никакой нет и делать ты ничего не собираешься, а тема чисто пофлудить?

Если статически собрано - то без хакерства никак. Либо получаешь два потока без синхронизации, либо мапишь их на один общий дескриптор, тогда они синхронизируются, но узнать где чей вывод уже нельзя.

firkax ★★★★★
()
Ответ на: комментарий от firkax

тема чисто пофлудить?

Как будто что-то плохое. strace вон перехватывает все системные вызовы, и всё у неё хорошо. И у меня было бы так же, если бы была возможность отключить буферизацию при выводе в stdout в статически слинкованной программе.

https://www.gnu.org/software/coreutils/manual/html_node/stdbuf-invocation.html

Shushundr ★★★★
() автор топика
Последнее исправление: Shushundr (всего исправлений: 2)
Ответ на: комментарий от firkax

программы создают проблемы?

да вот, да пожалуйста:
«For instance Go doesn’t use libc for IO, so stdbuf won’t affect most Go programs.»

Shushundr ★★★★
() автор топика

Можно ли в Linux узнать, в какой момент сообщение было записано в файловый поток?

Можно узнать, когда в файл была проведена последняя запись (время модификации). Судя по timespec, с точностью до наносекунд. Так что как вариант, постоянно вызывать stat и смотреть время записи. Это если нет возможности влезть в пишущую программу.

COKPOWEHEU
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.