/* Операция прошла неуспешно; в случае ошибки функция chown()
должна вернуть значение -1. */
assert(rval == -1);
/* Проверяем значение переменной errno и выполняем
соответствующее действие. */
switch (error_code) {
case EPERM: /* Доступ запрещен. */
case EROFS: /* Переменная PATH ссылается на файловую
систему, доступную только для чтения. */
case ENAMETOOLONG: /* Переменная PATH оказалась слишком длинной. */
case ENOENT: /* Переменная PATH ссылается на
несуществующий файл. */
case ENOTDIR: /* Один из компонентов переменной PATH
не является каталогом. */
case EACCES: /* Один из компонентов переменной PATH
недоступен. */
/* Что-то неправильно с файлом, выводим сообщение
об ошибке. */
fprintf(stderr, "error changing ownership of %s: %s\n",
path, strerror(error_code));
/* He завершаем программу; можно предоставить пользователю
шанс открыть другой файл. */
break;
case ЕFAULT:
/* Переменная PATH содержит неправильный адрес. Это, скорее
всего, ошибка программы. */
abort();
case ENOMEM:
/* Ядро столкнулось с нехваткой памяти. */
fprintf(stderr, "%s\n", strerror(error_code));
exit(1);
default:
/* Произошла какая-то другая, непредвиденная ошибка. Мы
пытались обработать все возможные коды ошибок. Если
что-то пропущено, то это ошибка программы! */
abort();
};
}
В самом начале программного фрагмента можно было поставить следующий код:
rval = chown(path, user_id, -1);
assert(rval == 0);
Но в таком случае, если функция завершится неуспешно, у нас не будет возможности обработать или исправить ошибку и даже просто сообщить о ней. Какую форму проверки использовать — зависит от требований к обнаружению и последующему исправлению ошибок в программе.
2.2.4. Ошибки выделения ресурсов
Обычно при неудачном выполнении системного вызова наиболее приемлемое решение — отменить текущую операцию, но не завершить программу, так как можно восстановить ее нормальную работу. Один из способов сделать это — выйти из текущей функции, передав через оператор return
код ошибки вызывающему модулю.
В случае, когда выход осуществляется посреди функции, важно убедиться в том, что ресурсы, выделенные в функции ранее, освобождены. К таким ресурсам относятся буферы памяти, дескрипторы и указатели файлов, временные файлы, объекты синхронизации и т.д. В противном случае, если программа продолжит выполняться, ресурсы окажутся потерянными.
В качестве примера рассмотрим функцию, загружающую содержимое файла в буфер. Функция выполняет такую последовательность действий:
1. выделяет буфер;
2. открывает файл;
3. читает содержимое файла и записывает его в буфер;
4. закрывает файл;
5. возвращает буфер вызывающему модулю.
Если файл не существует, этап 2 закончится неудачей. Подходящая реакция в этом случае — вернуть из функции значение NULL
. Но если буфер уже был выделен на этапе 1, существует опасность потери этого ресурса. Нужно не забыть освободить буфер где-то в программе. Если же неудачей завершится этап 3, требуется не только освободить буфер перед выходом из функции, но и закрыть файл.
В листинге 2.6 показан пример реализации такой функции.
#include
#include
#include
#include
#include
char* read_from_file(const char* filename, size_t length) {
char* buffer;
int fd;
ssize_t bytes_read;
/* Выделяем буфер. */
buffer = (char*)malloc(length);
if (buffer == NULL)
return NULL;
/* Открываем файл. */
fd = open(filename, O_RDONLY);
if (fd == 1) {