Сначала O_CREAT
создает файл, если он не существует, а O_CREAT|O_EXCL
создает файл и завершается ошибкой, если он уже существует.
Во-вторых, (strncmp(mode, "w", 1) == 0)
эквивалентно (mode[0] == 'w')
, что, вероятно, не соответствует вашим ожиданиям. Вы, вероятно, имели в виду (strchr(mode, "w"))
вместо.
Рассмотрим следующую реализацию (полный пример программы):
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Internal flags used by custom_fopen(): */
#define FM_R (1<<0) /* r, w+: For reading */
#define FM_W (1<<1) /* w, r+: For writing */
#define FM_TRUNC (1<<2) /* w, w+: Truncate */
#define FM_CREAT (1<<3) /* w, r+: Create if necessary */
#define FM_EXCL (1<<4) /* x: Fail if already exists */
#define FM_APPEND (1<<5) /* a: Append */
#define FM_CLOEXEC (1<<6) /* e: Close-on-exec() */
#define FM_SYMLINK (1<<7) /* s: Fail if last path component is a symlink */
#define FM_RW (FM_R | FM_W) /* r+, w+ */
FILE *custom_fopen(const char *path, const char *mode)
{
const char *fdmode;
int fm, flags, fd, saved_errno;
FILE *ret;
if (!path || !*path || !mode) {
errno = EINVAL;
return NULL;
}
switch ((strchr(mode, 'r') ? 1 : 0) +
(strchr(mode, 'w') ? 2 : 0) +
(strchr(mode, 'a') ? 4 : 0) +
(strchr(mode, '+') ? 8 : 0)) {
case 1: fdmode = "r"; fm = FM_R; break;
case 2: fdmode = "w"; fm = FM_W | FM_CREAT | FM_TRUNC; break;
case 4: fdmode = "a"; fm = FM_W | FM_CREAT | FM_APPEND; break;
case 9: fdmode = "r+"; fm = FM_RW | FM_CREAT; break;
case 10: fdmode = "w+"; fm = FM_RW | FM_CREAT | FM_TRUNC; break;
case 12: fdmode = "a+"; fm = FM_RW | FM_CREAT | FM_APPEND; break;
default:
/* Invalid combination of 'r', 'w', 'a', and '+'. */
errno = EINVAL;
return NULL;
}
if (strchr(mode, 'x')) {
if (fm & FM_CREAT)
fm |= FM_EXCL;
else {
/* 'rx' does not make sense, and would not work anyway. */
errno = EINVAL;
return NULL;
}
}
if (strchr(mode, 'e'))
fm |= FM_CLOEXEC;
if (strchr(mode, 's'))
fm |= FM_SYMLINK;
/* Verify 'mode' consists of supported characters only. */
if (strlen(mode) != strspn(mode, "rwa+xesb")) {
errno = EINVAL;
return NULL;
}
/* Map 'fm' to 'flags' for open(). */
switch (fm & FM_RW) {
case FM_R: flags = O_RDONLY; break;
case FM_W: flags = O_WRONLY; break;
case FM_RW: flags = O_RDWR; break;
default:
errno = EINVAL;
return NULL;
}
if (fm & FM_TRUNC) flags |= O_TRUNC;
if (fm & FM_CREAT) flags |= O_CREAT;
if (fm & FM_EXCL) flags |= O_EXCL;
if (fm & FM_APPEND) flags |= O_APPEND;
if (fm & FM_CLOEXEC) flags |= O_CLOEXEC;
if (fm & FM_SYMLINK) flags |= O_NOFOLLOW;
/* Open the file. If we might create it, use mode 0666 like fopen() does. */
if (fm & FM_CREAT)
fd = open(path, flags, 0666);
else
fd = open(path, flags);
/* Failed? */
if (fd == -1)
return NULL; /* errno set by open() */
/* Convert the file descriptor to a file handle. */
ret = fdopen(fd, fdmode);
if (ret)
return ret;
/* Failed. Remember the reason for the failure. */
saved_errno = errno;
/* If we created or truncated the file, unlink it. */
if (fm & (FM_EXCL | FM_TRUNC))
unlink(path);
/* Close the file descriptor. */
close(fd);
/* Return, recalling the reason for the failure. */
errno = saved_errno;
return NULL;
}
int main(int argc, char *argv[])
{
FILE *handle;
if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s PATH MODE\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
handle = custom_fopen(argv[1], argv[2]);
if (!handle) {
const int err = errno;
fprintf(stderr, "custom_fopen(\"%s\", \"%s\") == NULL, errno = %d: %s.\n",
argv[1], argv[2], err, strerror(err));
return EXIT_FAILURE;
}
if (fclose(handle)) {
const int err = errno;
fprintf(stderr, "fclose(custom_fopen(\"%s\", \"%s\")) failed, errno = %d: %s.\n",
argv[1], argv[2], err, strerror(err));
return EXIT_FAILURE;
}
printf("custom_fopen(\"%s\", \"%s\"): Success.\n", argv[1], argv[2]);
return EXIT_SUCCESS;
}
#define _POSIX_C_SOURCE 200809L
указывает вашей библиотеке C (по крайней мере, совместимой с GNU C) раскрыть функции POSIX.1-2008 (например, open()
).
Поведение режимов r
, w
, a
, r+
, w+
и a+
такое же, как описано в man 3 fopen
. По крайней мере один из них должен быть в mode
. (+
не обязательно должен следовать сразу за буквой.)
Вышеприведенная реализация дополнительно поддерживает b
(ничего не делает, для POSIX), x
(ошибка при создании нового файла, но он уже существует), s
(ошибка, если часть имени пути является символической ссылкой ) и e
(закройте дескриптор в exec, не передавая его дочерним процессам).
Первый оператор switch
обрабатывает основной режим, игнорируя порядок символов. По сути, он проверяет, какой из четырех символов rwa+
существует в mode
, и принимает только разумные комбинации. Вызовы strchr(mode, 'c')
возвращают ненулевой указатель (логическое значение true) тогда и только тогда, когда mode
содержит 'c'
.
Предложение if
, следующее за ним, обнаруживает x
в режиме. Комбинация x
и r
не допускается, потому что это не имеет смысла. (POSIX.1 говорит, что поведение с open()
с O_RDONLY | O_EXCL
не определено.)
(strlen(mode) == strspn(mode, "rwa+xesb"))
проверяет, что mode
состоит только из букв r
, w
, a
, +
, x
, e
, s
и b
; они могут повторяться или в любом порядке. Эта проверка отклоняет неподдерживаемые символы.
Второй оператор switch
и предложения if
отображают fm
в flags
. Мы делаем это, потому что O_
константы могут не быть единичными битами, что означает, что тесты типа (flags & O_RDONLY)
, (flags & O_WRONLY)
и (flags & O_RDWR)
не являются надежными и на самом деле не будут работать так, как можно было ожидать. Вместо этого мы используем fm
и наши собственные однобитовые константы FM_
, которые мы можем рассматривать как маски, и просто сопоставляем их с соответствующими значениями flags
позже. (Проще говоря, fm
отслеживает нужные нам функции, и мы присваиваем только соответствующий набор флагов flags
, и никогда не проверяем flags
.)
Если мы можем создать файл, мы используем режим 0666
(rw-rw-rw-
), измененный пользователем umask
. (Обычно обычные пользователи имеют umask 002
, 007
, 022
или 077
, что приводит к режимам получения новых файлов 0664
(rw-rw-r--
), 0660
(rw-rw----
) , 0644
(rw-r--r--
) или 0600
(rw-------
) соответственно.)
Это именно то, что делает fopen()
тоже.
Когда у нас есть дескриптор открытого файла, мы все равно должны связать его с дескриптором потока. Мы делаем это, используя fdopen()
. Обратите внимание, что мы выбрали правильный mode
для этого вызова в первом операторе switch
. Если этот вызов завершится успешно, потоку будет «принадлежать» дескриптор файла, и все, что нам нужно сделать, это вернуть возвращенный дескриптор потока fdopen()
.
Если fdopen()
завершится неудачно, нам нужно закрыть дескриптор файла. Мы также можем решить удалить / отменить связь с файлом. Приведенный выше код удаляет файл, если мы уверены, что он не существовал ранее (e
) или если мы урезали его (w
, w+
), так как тогда данные, которые он мог содержать, в любом случае были бы потеряны.
Тестовая программа принимает два параметра командной строки: путь или имя файла и строку режима. Программа выполняет вызов custom_fopen(pathorfilename, modestring)
и сообщает о результатах. (Если вызов custom_fopen()
успешен, он также проверяет, что соответствующий вызов fclose()
успешен, потому что иногда проблемы, связанные с дескриптором файла (или флаги несовместимого режима для вызовов open()
/ fdopen()
), могут наблюдаться только в первая сделанная операция, или при закрытии потока.
Я только слегка протестировал вышеупомянутую функцию и программу, и ошибки всегда возможны. Если вы обнаружите ошибку или у вас возникнут проблемы с кодом, сообщите мне об этом в комментарии, чтобы я мог проверить и исправить при необходимости.