Марк Митчелл - Программирование для Linux. Профессиональный подход
#include <stdio.h>
#include <stdlib.h>
int main() {
char* server_name = getenv("SERVER_NAME");
if (server_name == NULL)
/* переменная среды SERVER_NAME не задана. Используем
установки по умолчанию. */
server_name = "server.my-company.com";
printf("accessing server %sn", server_name);
/* Здесь осуществляется доступ к серверу... */
return 0;
}
Допустим, программа называется client. Если переменная SERVER_NAME не задана, используется имя сервера, заданное по умолчанию:
% client
accessing server server.my-company.com
Вот как задается другой сервер:
% export SERVER_NAME=backup-server.elsewhere.net
% client
accessing server backup-server.elsewhere.net
2.1.7. Временные файлы
Иногда программе требуется создать временный файл, например для промежуточного хранения большого объема данных или для передачи данных другой программе. В системах GNU/Linux временные файлы хранятся в каталоге /tmp. Работая с временными файлами, необходимо помнить о следующих ловушках.
■ Одновременно может быть запущено несколько экземпляров программы (одним и тем же пользователем или разными пользователями). Все они должны использовать разные имена временных файлов, чтобы не было конфликтов.
■ Права доступа к временным файлам должны задаваться таким образом, чтобы неавторизованные пользователи не могли влиять на работу программы путем модификации или замены временного файла.
■ Имена временных файлов должны генерироваться так, чтобы посторонние пользователи не могли их предугадать. В противном случае хакер может воспользоваться задержкой между проверкой факта использования данного имени файла и открытием нового временного файла.
В Linux имеются функции mkstemp() и tmpfile(), решающие все вышеперечисленные проблемы. Выбор между ними делается на основании того, должен ли временный файл передаваться другой программе и какие функции ввода-вывода будут применяться при работе с файлом: низкоуровневые (read(), write() и т.д.) или потоковые (fopen(), fprintf() и т.д.).
Функция mkstemp()Функция mkstemp() генерирует уникальное имя файла на основании переданного ей шаблона, создает временный файл с правами, разрешающими доступ к нему только для текущего пользователя, и открывает файл в режиме чтения/записи. Шаблон имени — это строка, оканчивающаяся последовательностью "XXXXXX" (шесть прописных букв "X"). Функция mkstemp() заменяет каждую букву произвольным символом таким образом, чтобы получилось уникальное имя, и возвращает дескриптор файла. Запись в файл осуществляется с помощью функций семейства write().
Временные файлы, создаваемые функцией mkstemp(), не удаляются автоматически. Ответственность за это возлагается на того, кто запускает программу. (Программисты должны внимательно следить за удалением временных файлов, иначе файловая система /tmp рано или поздно переполнится, приведя всю систему в нерабочее состояние.) Если файл создан для внутреннего использования и не предназначен для передачи другой программе, по окончании работы с ним нужно сразу же вызвать функцию unlink(). Она удаляет из каталога ссылку на файл, но сам файл остается до тех пор, пока не будут закрыты все ссылающиеся на него дескрипторы. Таким образом, программа может продолжать использовать временный файл; он будет удален автоматически сразу после закрытия дескриптора. Операционная система закрывает дескрипторы файлов по окончании работы программы, так что временный файл будет удален даже в случае аварийного завершения программы.
В листинге 2.5 показаны две функции, работающие с временным файлом. Будучи примененными в связке, они позволяют легко переносить содержимое буферов из операторной памяти во временный файл (это дает возможность освобождать и повторно использовать память), а затем загружать данные из файла обратно в память.
Листинг 2.5. (temp_file.c) Использование функции mkstemp()#include <stdlib.h>
#include <unistd.h>
/* дескриптор временного файла, созданного в функции
write_temp_file(). */
typedef int temp_file_handle;
/* Запись указанного числа байтов из буфера во временный файл.
Ссылка на временный файл немедленно удаляется. Возвращается
дескриптор временного файла. */
temp_file_handle write_temp_file(char* buffer, size_t length) {
/* Создание имени файла и самого файла. Цепочка XXXXXX будет
заменена символами, которые сделают имя уникальным. */
char temp_filename() = "/tmp/temp_file.XXXXXX";
int fd = mkstemp(temp_filename);
/* немедленное удаление ссылки на файл, благодаря чему он будет
удален сразу же после закрытия дескриптора файла. */
unlink(temp_filename);
/* Сначала в файл записывается число, определяющее размер
буфера. */
write(fd, &length, sizeof(length));
/* теперь записываем сами данные. */
write(fd, buffer, length);
/* Возвращаем дескриптор файла. */
return fd;
}
/* Чтение содержимого временного файла, созданного в функции
write_temp_file(). Создается и возвращается буфер с содержимым
файла. Этот буфер должен быть удален в вызывающей подпрограмме
с помощью функции free(). В параметр LENGTH записывается размер
буфера в байтах. В конце временный файл удаляется. */
char* read_temp_file(temp_file_handle temp_file, size_t* length) {
char* buffer;
/* TEMP_FILE -- это дескриптор временного файла. */
int fd = temp_file;
/* переход в начало файла. */
lseek(fd, 0, SEEK_SET);
/* Определение объема данных, содержащихся во временном файле. */
read(fd, length, sizeof(*length));
/* Выделение буфера и чтение данных. */
buffer = (char*)malloc(*length);
read(fd, buffer, *length);
/* Закрытие дескриптора файла, что приведет к уничтожению
временного файла. */
close(fd);
return buffer;
}
Функция tmpfile()Если в программе используются функции потокового ввода-вывода библиотеки языка С и передавать временный файл другой программе не нужно, то для работы с временным файлом больше подойдет функция tmpfile(). Она создает и открывает временный файл, возвращая файловый указатель на него. Ссылка на файл уже оказывается удаленной, благодаря чему он уничтожается автоматически при закрытии указателя (с помощью функции fclose()) или при завершении программы.
В Linux есть ряд других функций, предназначенных для генерирования временных файлов или их имен, в частности mktemp(), tmpnam() и tempnam(). Работать с ними нежелательно, поскольку возникают упоминавшиеся выше проблемы, связанные с надежностью и безопасностью.
2.2. Защита от ошибок
Написать программу, которая корректно работает при "разумном" использовании, — трудная задача. Написать программу, которая ведет себя "разумно" при возникновении ошибок, — еще труднее. В этом разделе описываются методики программирования, позволяющие выявлять ошибки на ранних стадиях и решать проблемы, возникающие в ходе выполнения программы.
В представленные ниже фрагменты программ сознательно не включены громоздкие коды проверки ошибок и восстановления после них. так как это привело бы к потере наглядности при рассмотрении основных методик. Тем не менее мы вернемся к данной теме в главе 11, "Демонстрационное Linux-приложение", где будут приведены полностью работающие программы.
2.2.1. Макрос assert()
При написании программы следует помнить о том, что ошибки и непредвиденные ситуации могут радикально менять работу программы на самых ранних стадиях ее выполнения. Нужно стараться выявлять такие ошибки как можно раньше, на этапах разработки и отладки. Остальные ошибки, влияние которых на работу программы незначительно, обычно остаются незамеченными до тех пор, пока пользователи не начнут запускать программу.
Простейший способ выявления ненормальных ситуаций — стандартный макрос assert() в языке С. Его аргументом является булево выражение. Программа завершается, если выражение оказывается ложным, при этом выводится сообщение об ошибке с указанием исходного файла и номера строки, а также текста выражения, приведшего к ошибке. Макрос assert() оказывается очень полезным для самых разных проверок целостности, выполняемых внутри программы. Например, с помощью этого макроса проверяют правильность аргументов функций, выполнение входных и выходных условий при вызове функций (а также методов C++) и наличие непредвиденных возвращаемых значений.