Большинство системных вызовов (например работа с файлами) возвращают код завершения, впрочем, как и любые другие, хорошо сделанные программы. А как иначе понять успешно завершилось или нет? Поэтому возвращается либо 0 - успех, либо другое число, например -1 или 1.
Однако с этим числом, кроме бинарного понимания "успех/сбой" ничего не поймешь.
Когда мы получили сбой, получив, например -1, хотелось бы узнать что конкретно произошло.
Для диагностирования проблемы, существует специальная переменная errno, ей присваиваются специальные коды ошибок, например для ошибки, что файл уже существует есть константа со значением EEXIST=17.
А где узнать значения ошибок?
Узнать их можно использовав справочник:
errno -lИли же получить значение ошибки в строковую переменную, через функцию
strerror()из заголовка<string.h>:char* error_string = strerror(errno);
create_file
Создает пустой файл с именем указанным в аргументе, если файл существует - бросает ошибку, если файл не может быть создан - бросает ошибку.
Пример выполнения
Обычное создание пустого файла:
./create_file.out kekfile.txtОшибка повторного создания файла:
./create_file.out kekfile.txt file creation failed. file already exists! strerror() result: File existsОшибка создания файла в директории, где нет прав на создание (корневая директория
/) :./create_file.out /kekfile.txt file creation failed. permission denied! strerror() result: Permission denied
- использование системного вызова
open()для создания файла. - определение ошибок и вывод их на экран.
public/code_sources/linux-c-system-programming-essentials/8-system-calls/error_handling/create_file.c
int open_code = open(argv[1], O_CREAT | O_EXCL | O_WRONLY, 0644);Функция open() в данном случае принимает три параметра:
filename- путь к файлу, который нужно создать, например/home/documents/hello.txt.O_CREAT | O_EXCL | O_WRONLY- три флага, указывающих что делать при вызовеopen().
O_CREAT
Указывает на необходиомость создания файла, если он еще не существует.
O_EXCL
Эксклюзивность, работаем только если файла еще нет.
O_WRONLY
Открыть файл только для записи.
0644- права доступа к созданному файлу.
6 = 4+2 = rw-(чтение + запись для владельца)
4 = r--(чтение для группы)
4 = r--(чтение для остальных)
Итого, файл будет доступен для записи текущему пользователю.
Сперва проверяем, что произошел сбой у вызова open():
if(open_code == -1)Если сбой произошел, то уже можно проверять конкретные ошибки из заголовка <errno.h>:
if(errno == EACCES)
{
fprintf(stderr, "permission denied!\n");
}
else if(errno == EEXIST)
{
fprintf(stderr, "file already exists!\n");
}
else
{
fprintf(stderr, "unknown errno: %d\n", errno);
}Как видим, мы можем четко определить что за ошибка, сравнивая переменную errno с соответствующими константами из заголовка <errno.h>.
Если мы хотим получить строковое представление текущей ошибки из errno , то существует функция strerror() из заголовка <string.h>:
fprintf( stderr, "strerror() result: %s\n", strerror(errno) );Таким образом, мы можем преобразовать числовую константу из списка ошибок в понятную строку, например, если errno имеет значение 17 равное константе EEXIST, то получим следующую строку:
"File exists"Еще есть функция perror(), которая сразу же пишет сообщение об ошибке. Внутри себя она использует переменную errno и выводит ее текстовое представление в вывод stderr.
И сразу становится понятно в чем дело. Поэтому функция perror() это наш лучший друг в диагностировании ошибок.
Следует не путать потоки данных (stream) и потоки выполнения (thread).
В этом разделе мы будем работать с потоками данных (streams), далее я буду называть их стримами.
stream
Это высокоуровневая абстракция, построенная над файловыми дескрипторами , предоставляет удобный интерфейс для работы с потоками данных, для чтения/записи в файлы (в Linux все является файлами!).
Стримы в языке Си предоставлены структуройFILEиз заголовка<stdio.h>.
file_stream_write
Принимает текст от пользователя и записывает его в файл.
Пример выполнения
./file_stream_write.out goga.txt giga boy hi how are you doing? # CTRL + D чтобы закончить ввод # читаем получившийся файл: cat goga.txt giga boy hi how are you doing?
- Использование стримов для ввода пользователя и вывода в файл.
public/code_sources/linux-c-system-programming-essentials/8-system-calls/file_streams/file_stream_write.c
Объявляем переменную типа FILE* и строковый буфер для считывания ввода пользователя:
FILE* file = NULL;
char line_buffer[LINE_BUFFER_SIZE] = { 0 };Затем открываем файл через функцию fopen():
file = fopen(argv[1], "w");Далее получаем ввод от пользователя, записываем его ввод в файл:
while( fgets( line_buffer, sizeof(line_buffer), stdin ) != NULL )
{
fprintf( file, line_buffer );
}И закрываем файл в конце программы:
fprintf( file, line_buffer );Возможность делать вывод в двоичном формате
Для уменьшения размера файла, или для его "шифрования" можно делать запись в двоичном формате.
Для этого мы делаем формат открытия файла в виде"wb":file = fopen(argv[1], "wb")А запись файла делать с помощью функции
fwrite(), она делает ввод двоичным, и имеет такую сигнатуру:size_t fwrite(const void ptr[restrict .size * .nmemb], size_t size, size_t nmemb, FILE *restrict stream);
Изначально, до появления стримов, подобные операции делались с помощью вызовов open() , read() и write(), все они работают с файловыми дескрипторами целочисленного типа int. Из минусов такого подхода можно описать следующее:
- Больше кода - вам нужно создавать множество буферов, делать больше вызовов. Больше кода = больше ошибок. Более того, функциями ввода/вывода стримов вроде
fprintf()иfgets()удобнее пользоваться, вместо ручного копирования байтов вручную. - Отсутствие буферизации - в объект
FILEуже встроена буферизация, вам не нужно создавать ее самому. - Производительность - внутри операций с
FILEвродеfopen()иfprintf()происходит меньше системных вызовов ввода/вывода, из-за встроенной буферизации.