Захват stdout в zip и прерывание с помощью CTRL- C дает поврежденный zip файл - PullRequest
2 голосов
/ 15 февраля 2020

Я занимаюсь разработкой программы на C ++, которая может работать весь день. Он выводит на стандартный вывод, и я хочу сжать этот вывод. Несжатый вывод может быть много ГБ. Запускаемый сценарий оболочки Bourne компилирует код C ++ и запускает программу следующим образом:

./prog | gzip > output.gz

Когда я прерываю скрипт с помощью CTRL- C, файл .gz всегда поврежден. Когда я запускаю программу из терминала и прерываю ее, используя CTRL- C, файл .gz также всегда поврежден. Когда я запускаю программу терминала и завершаю его с помощью Linux killall, файл .gz в порядке.

С другой стороны, на терминале cat <large_file> | gzip > cat.gz может быть прерван с помощью CTRL- C и На cat.gz всегда хорошо. Поэтому я подозреваю, что у cat есть какой-то обработчик сигналов, который я должен также реализовать в своей программе на C ++ ... но, глядя на онлайн-реализацию cat, я не нашел ничего подобного. Тем не менее, я реализовал это:

void SignalHandler(int aSignum)
{
  exit(0);
}

void Signals()
{
  signal(SIGINT,  SignalHandler);
  signal(SIGKILL, SignalHandler);
  signal(SIGTERM, SignalHandler);
}

... и даже что-то в скрипте b sh, но ничего не помогает. После CTRL- C файл gz поврежден.

Вопросы:

  • Что у кошки есть, чего нет у моей программы?
  • Как мне прекратить работу? мой скрипт / программа, использующая CTRL- C с файлом zip по порядку?

Edit 1

Открытие результирующего файла с помощью zcat дает некоторый вывод, но затем: gzip: file.gz: unexpected end of file. Если открыть его в Менеджере архивов в Ubuntu, появится всплывающее сообщение: An error occurred while extracting files.

Edit 2

Пробная очистка; никаких изменений в проблеме не наблюдалось.

Изменить 3

Больше информации о проблеме: Подпись отсутствующего конца (EOCDR)

Fix archive (-F) - assume mostly intact archive
    zip warning: bad archive - missing end signature
    zip warning: (If downloaded, was binary mode used?  If not, the
    zip warning:  archive may be scrambled and not recoverable)
    zip warning: Can't use -F to fix (try -FF)

zip error: Zip file structure invalid (file.gz)
maot@HP-Pavilion-dv7:~/temp$ zip -FF file.gz --out file2.gz
Fix archive (-FF) - salvage what can
    zip warning: Missing end (EOCDR) signature - either this archive
                     is not readable or the end is damaged
Is this a single-disk archive?  (y/n): y
  Assuming single-disk archive
Scanning for entries...
    zip warning: zip file empty
maot@HP-Pavilion-dv7:~/temp$ ls -lh file2.gz
-rw------- 1 maot maot 22 feb 15 15:18 file2.gz
maot@HP-Pavilion-dv7:~/temp$ 

Изменить 4

Спасибо @ Максим Егорушкин, но это не работает. Прерывание сценария с помощью CTRL- C убивает prog перед выполнением обработчика сигнала сценария. Следовательно, я не могу послать ему сигнал, он уже пропал ... и без вывода SignalHandler. Когда prog запускается из командной строки, выводится SignalHandler. Прога:

#include <iostream>
#include <unistd.h>
#include <csignal>

void SignalHandler(int aSignum)
{
  std::cout << "prog: Interrupt signal " << aSignum << " received.\n";
  fflush(nullptr);
  exit(0);
}

int main()
{
  for (int sig = 1; sig <=31; sig++)
  {
    std::cout << " sig " << sig;
    signal(sig,  SignalHandler);
  }

  while (true)
  {
    std::cout << "prog: Sleep ";
    fflush(nullptr);
    usleep(1e4);
  }
}

Скрипт:

#!/bin/sh

onerror()
{
  echo "onerror(): Started."
  ps -jef | grep prog
  killall -s SIGINT prog
  exit
}

g++ -Wall prog.cpp -o prog

trap onerror 2

prog | gzip > file.gz

Результат:

maot@HP-Pavilion-dv7:~/temp$ test.sh 
^Conerror(): Started.
maot     16733 16721 16721  5781  0 16:17 pts/1    00:00:00 grep prog
prog: no process found
maot@HP-Pavilion-dv7:~/temp$ 

Редактирование 5 минимальных рабочих решений

Реализация ответа Максима Егорушкин. Скрипт:

#!/bin/sh
g++ -Wall prog.cpp -o prog
prog | setsid gzip > file.gz & wait

Прог:

#include <iostream>
#include <unistd.h>
#include <csignal>

void SignalHandler(int aSignum)
{
  std::cout << "prog: Interrupt signal " << aSignum << " received.\n";
  exit(0);
}

int main()
{
  signal(SIGINT,  SignalHandler);

  while (true)
  {
    std::cout << "prog: Sleep ";
    usleep(1e4);
  }
}

1 Ответ

2 голосов
/ 15 февраля 2020

Когда вы нажимаете Ctrl + C, оболочка отправляет SIGINT в последний процесс в конвейере, который здесь равен gzip. gzip завершается, и в следующий раз prog записывает в stdout, получает SIGPIPE.

Вам необходимо отправить SIGINT на prog, чтобы передать гриппу sh его stdout и выйти (при условии, что вы установили обработчик сигнала, как вы это сделали), чтобы gzip получил все его вывод и затем завершается.


Вы можете запустить свой конвейер следующим образом:

prog | setsid gzip > file.gz & wait

Он использует функцию управления заданиями оболочки, чтобы запустить конвейер в фоновом режиме (это & символ). Тогда это wait s для завершения работы. На Ctrl+C SIGINT отправляется процессу переднего плана, который является оболочкой в ​​wait, и все процессы в одной группе процессов терминала (в отличие от того, когда конвейер находится на переднем плане, а SIGINT отправляется только последнему процессу в трубопроводе). prog в этой группе. Но gzip начинается с setsid, чтобы поместить его в другую группу, так что он не получает SIGINT, а завершается, когда его stdin закрывается, когда prog завершается.

...