Файлы существуют в нескольких качествах:
В файловой системе UNIX хранятся различные объекты: файлы, каталоги, символические ссылки, файлы устройств, FIFO, сокеты. Объекты в ФС адресуются именами и характеризуются правами доступа. Один объект может иметь несколько имен. Права доступа (R)ead, (W)rite, e(X)ecute определены по отдельности для трех категорий пользователей (U)ser, (G)roup, (O)ther. Дополнительно к правам доступа есть флаг смены владельца на время выполнения файла, смены группы на время выполнения файла и признак «липкости», что бы он не означал. Права доступа и флаги для разных типов объектов интерпретируются немного по-разному.
Файлы можно создавать (одновременно давая имя), добавлять новые имена, удалять старые имена, а также менять права доступа к файлу (влияет на сам файл, вне зависимости от того к какому из его имен применялась операция). Администратор может еще и поменять владельца файла.
Файл оставшийся без имени и не используемый для вода/вывода или в качестве источника данных программы – уничтожается. Вызовы ядра Unix, связанные с файловой системой можно условно поделить на несколько групп:
Метаданные:
int chown(const char *path, uid_t owner, gid_t group); //смена владельца и группы
int chmod(const char *path, mode_t mode); //смена прав доступа
int stat(const char *file_name, struct stat *buf); //получение всех атрибутов файла
Имена в каталогах:
int mkdir(const char *pathname, mode_t mode); //создание каталога
int rmdir(const char *pathname); //удаление каталога
int link(const char *oldpath, const char *newpath); //создание нового имени
int unlink(const char *pathname); //удаление старого имени
int rename(const char *oldpath, const char *newpath); //атомарная операция удаляющая имя oldpath и создающая newpath
Создание символических ссылок, FIFO и файлов устройств:
int symlink(const char *oldpath, const char *newpath); //создание файла ссылки newpath ссылающегося на oldpath
int mknod(const char *path, mode_t mode, dev_t dev); //создание FIFO или файла устройства (определяется mode)
Открытые файлы = файловые дескрипторы
В качестве источника данных файлы представлены в программе в виде файловых дескрипторов – целых чисел являющимися индексами в таблице открытых файлов. За файловыми дескрипторами могут скрываться файлы, FIFO и файлы устройств в ФС, неименованые каналы - pipe или сокеты. Для операций чтения и записи все эти источники данных равноценны.
По соглашениям UNIX вновь запущенная программа может рассчитывать на три открытых файла с индексами 0,1,2, соответствующие stdin, stdout, stderr. Ответственность за это возлагается на программу вызвавшую exec().
Работа с метаданными файла может происходить и по имени и по файловому дескриптору.
int fchmod(int fd, mode_t mode);
int fchown(int fd, uid_t owner, gid_t group);
int fstat(int fd, struct stat *buf);
//Перенаправление стандартного файла ошибок
int newfd;
char *fname="file";
if( (newd = creat(fname, S_IRUSR|S_IWUSR) >=0 ) ){
dup2(newfd,2);
close(newfd);
}else{
perror("Cannot open new stderr file:");
exit(1);
}
// Создание большого "дырявого" файла, который имеет длину 10000000 байт а реально занимает 1 блок на диске
newd = open(fname, O_WRONLY|O_TRUNC);
lseek(fd,10000000,SEEK_SET);
write(fd," ",1);
//узнаем текущую позицию чтения/записи
pos=lseek(newfd,0,SEEK_CUR);
//делаем "невидимым" временный файл
int tmpfd;
char tmpname[]="/tmp/qqqXXXXXX"
mktemp(tmpname);
int tmpfs = open(tmpfname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)
unlink(tmpfname);
//теперь файл не имеет имени и будет уничтожен по завершению нашей программы
//читать и писать в него можно без проблем
write(tmpfd,tmpname,1);
lseek(tmpfd,0,SEEK_SET);
read(tmpfd,tmpname,1);
Работа с содержимым файла происходит через целочисленный файловый дескриптор, который представляет из себя номер строки в таблице ссылок на открытые файлы процесса.
При открытии файла в вызове ядра open() проверяются соответствие флагов и прав доступа к файлу.
//почти псевдокод
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int fd;
int flags;
mode_t mode;
// открытие (+создание) файла
fd=open("pathname", flags, mode);
//или
fd=creat("pathname", mode);
// при открытии существующего файла можно опустить параметр mode
fd=open("pathname", flags);
mode - права доступа к файлу, назначаемые в момент его создания. Чтобы нельзя было случайно создать файл со слишком свободным доступом, при создании файла производится побитовое умножение mode на битовую маску umask (mode & ~umask). mode и _umask__ удобно задавать в восьмеричном виде считая, что классические права доступа rwx соответствуют одной восьмеричной цифре. Например, права доступа rwxr-xr-- запишутся в восьмеричном виде как 0754. Типичная маска выглядит так ---w--w- или 022в восьмеричной записи. Такая маска отбирает права на запись у группы и остальных.
Системный вызов umask(mask) устанавливает новую маску и возвращает старую.
#include <sys/types.h>
#include <sys/stat.h>
mode_t old_mask=umask(new_mask);
Для удобства записи прав доступа существуют мнемонические макросы:
S_IRWXU 00700 User Read, Write,eXecute; S_IRUSR 00400 User Read; S_IWUSR 00200 User Write и т.д. S_IXUSR, S_IRWXG, S_IRGRP, S_IWGRP, S_IXGRP, S_IRWXO, S_IROTH, S_IWOTH, S_IXOTH.
flags - флаги уточняющие режим открытия файла. Флаги делятся на несколько групп:
При ошибке открытия файла возвращается -1 и в переменную errno заносится код ошибки. Возможные значения ошибки (не все):
#include <unistd.h>
// чтение/запись определенного числа байт
int fd
char buf[SIZE];
size_t count=SIZE;
ssize_t res;
res=read (fd, buf, count);
res=write(fd, buf, count);
Чтение и запись возвращают количество прочитанных/записанных байтов или -1. -1 не всегда означает ошибку.
Возможные варианты ответа при записи:
Возможные варианты ответа при чтении:
Ошибки чтения/записи:
Чтение/запись из/в фрагментированной памяти
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
} iov[SIZE];
res=readv (fd, iov, SIZE);
res=writev(fd, iov, SIZE);
Чтение/запись в определенную позицию. offset - смещение в байтах относительно начала файла.
res=pread (fd, buf, count, offset);
res=pwrite(fd, buf, count, offset);
res=preadv (fd, iov, int iovcnt, offset);
res=pwritev(fd, iov, int iovcnt, offset);
// закрытие файла
int retval=close(fd);
Ошибки при закрытии файла встречаются редко, но встречаются.
Для установки позиции/чтения записи в файле используются два параметра offset - смещение в байтах и whence - место от которого отсчитывается смещение. Возможные значения whence:
Пример:
#include <sys/types.h>
#include <unistd.h>
// установка позиции чтения/записи
off_t offset=100;
int whence=SEEK_END;
off_t pos=lseek(fd, offset, whence);
Возвращается установленная позиция или -1 в случае ошибки.
Сочетание offset=0 и whence=SEEK_CUR позволяет узнать текущую позицию чтения/записи.
С помощью lseek возможно перемещение указателя записи за конец файла. Многие ФС в такой ситуации не выделяют блоки хранения под пропущенные байты и создают "дырявые" файлы, занимающие на диске пространство меньше своей длины.
int fd=open("/tmp/sparse-file", O_WRONLY|O_CREAT|O_TRUNC, 0700);
off_t pos=lseek(fd, 1000000000, SEEK_SET);
int res=write(fd,"c",1);
В данном примере создаётся файл длиной примерно 1 ГБ, занимающий на диске один блок данных (например 512 Б).
В 32-х разрядных системах могут быть проблемы с большими файлами. В этом случае надо использовать вызов lseek64 и некоторые дополнительные трюки.
Возможно создание ссылки на файловый дескриптор.
#include <unistd.h>
int fd1=dup(oldfd);
int fd2=dup2(oldfd, newfd);
dup() - выбирает в таблице открытых файлов первую свободную строку и записывает ссылку на oldfd в неё, dup2() - закрывает файл, связанный с дескриптором newfd (если он был открыт) и записывает ссылку oldfd в newfd. В случае успеха возвращается файловый дескриптор, в случае ошибки -1.
В связи с тем, что в таблицу открытых файлов вписывается именно ссылка, у файловых дескрипторов oldfd и newfd всегда будет одна и та же позиция головки чтения/записи.
Типичное применение dup2() - это подмена стандартных дескрипторов 0,1,2 (stdin,stdout,stderr). oldfd в этом случае закрывается после создания ссылки. dup2() предпочтительнее чем dup(), т.к. выполняется атомарно, что может быть важно в многопоточной среде.
int newfd=open("file",O_RDONLY);
dup2(newfd,0);
close(newfd);
вариант с dup()
int newfd=open("file",O_RDONLY);
close(0);
dup(newfd);
close(newfd);
Флаги | файл существует | файл не существует |
---|---|---|
Без флагов | Нет ошибки | ENOENT |
O_CREAT | Нет ошибки | Нет ошибки |
O_CREAT+O_EXCL | EEXIST | Нет ошибки |
Создание нового процесса в Unix осуществляется системным вызовом fork()
который создаёт точную копию текущего процесса. Копируются все структуры данных в ядре и в пространстве пользователя, а значит и таблица открытых файлов. Таким образом дочерний процесс наследует все открытые файлы родительского процесса.
В таблице виртуальных Inode во время вызова fork()
счётчики числа открытий файлов увеличиваются на количество ссылающихся на них файловых дескрипторов нового процесса.
Вызов exec("exefile",...)
загружает в память существующего процесса код и данные из файла exefile. Все открытые файлы сохраняют своё состояние, кроме тех, которые помечены флагом "O_CLOEXEC". Помеченные файлы закрываются. Исполняемый файл exefile не занимает файловый дескриптор, но так же считается открытым, т.е. exec()
увеличивает счётчик числа открытий в таблице виртуальных Inode.
Благодаря цепочке вызовов fork()
- exec()
и наследованию открытых файлов shell при запуске внешней программы создаёт новый процесс с помощью fork()
, а в нём перед вызовом exec()
может переназначить файлы стандартного ввод-вывода, которые будут унаследованы запускаемой программой.
Стандартная библиотекой языка Си использует системные вызовы для доступа к файловой системе, однако добавляет к этим вызовам некоторую дополнительную обвязку - буферизацию, преобразование символов конца строки в текстовых файлах и т.п.
Если хочется воспользоваться стандартными библиотечными функциями fprintf, fscanf и т.п. то можно создать структуру FILE на основе существующего дескриптора:
FILE *fp=fdopen(int fd, const char *mode); //mode как в fopen вида “rb” или “w” и т.п.
Операция блокировки части файла необходима для организации совместного доступа к файлу из нескольких процессов. Главная задача блокировки - обеспечение атомарности чтения данных, т.е. гарантия того, что при чтении (возможно несколькими вызовами read()
) все данные будут принадлежать одной "версии" файла и не будут перезаписаны во время чтения. Соответственно бываю эксклюзивные блокировки со стороны писателя (я пишу в этот файл, другим в это время читать и писать бессмысленно) и разделяемые блокировки со стороны читателей (мы читаем, пожалуйста, не пишите).
В Unix традиционно используются рекомендательные (advisory) блокировки. Такие блокировки требуют от всех процессов, которые хотят получить доступ к файлу, вызова специальных функций для установки и/или проверки блокировок. Вызовы open()
, read()
и write()
про блокировки ничего не знают, и, в результате, процессы, которые явно не вызывают функции работы с блокировками не узнают об их существовании и могут нарушить логику совместной работы с файлом.
Отдельной проблемой является то, что разные способы установки блокировок никак не взаимодействуют друг с другом. Так блокировка установленная вызовом fcntl()
не может быть проверена вызовом flock()
. Единственным реальным сценарием применения файловых блокировок в Unix является разработка комплекса программ с заранее прописанным алгоритмом синхронизации доступа к файлам через блокировки.
Хорошая статья на английском.
Самые ранние версии Unix не поддерживали блокировок, поэтому в различных программах можно встретить создание вспомогательного файла с расширением .lck или .lock. Перед записью в файл somefile.txt программа в цикле пытается создать файл somefile.txt.lck с флагами O_CREAT|O_EXCL. Если файл уже существует, то вызов open()
возвращает ошибку и цикл продолжается. Если файл удалось успешно создать, то цикл завершается и можно открывать на запись основной файл. В служебный файл блокировки часто пишут PID процесса, чтобы было понятно, кто его создал.
int fd=-1;
while(fd==-1){
fd=open("somefile.lck", O_CREAT|O_EXCL|O_WRONLY,0500);
}
//Пишем в файл блокировки свой PID
char pid[6];
itoa(getpid(), pid, 10);
write(fd,pid,strlen(pid));
//этот файловый дескриптор нам больше не нужен
close(fd);
//основная работа
int mainfd=open("somefile", O_WRONLY,0500);
...
close(mainfd);
//Удаляем файл блокировки
unlink("somefile.lck");
Следующим шагом стало появление в BSD системах вызова flock()
, который позволял пометить в ядре файл как заблокированный. Этот вызов не стандартизован POSIX, но поддерживается в Linux и во многих версиях Unix. flock()
не поддерживается сетевой файловой системой NFS.
#include <sys/file.h>
int flock(int fd, int operation);
Операции:
flock()
возвращает -1, а переменная errno
устанавливается в значение EWOULDBLOCK.fcntl()
Стандарт POSIX определяет операции с блокировками записей - участков файлов. Блокировки производятся универсальный вызов управления открытыми файлами fcntl()
(file control) или через функцию locf()
. В Linux locf()
это библиотечная обёртка для fcntl()
.
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock *lock);
fcntl()
- это перегруженный (в смысле C++) вызов ядра с переменным числом параметров. Его поведение определяется значением второго параметра - cmd. За блокировки отвечают команды F_SETLK, F_SETLKW и F_GETLK, которые используются для установки/снятия (SET) и чтения (GET) блокировок файла .
Параметры блокировки задаются/считываются через структуру flock. Блокируемая позиция задаётся параметром, указывающим откуда отсчитывать стартовое смещение, стартовым смещением и размером. Нулевой размер означает блокировку участка файла от стартового смещения и до бесконечности.
struct flock {
...
short l_type; /* Тип блокировки: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* Как интерпретировать l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Начальное смещение для блокировки */
off_t l_len; /* Количество байт для блокировки */
pid_t l_pid; /* PID процесса блокирующего нашу блокировку
(F_GETLK only) */
...
};
Тип блокировки:
Команды:
fcntl()
вернёт -1 и установит значение errno в EACCES или EAGAIN.lockf()
Функция lockf()
в Linux реализуется через fcntl()
и представляет из себя упрощённый интерфейс для работы с блокировками.
#include <unistd.h>
int lockf(int fd, int cmd, off_t len);
lockf()
работает с участком файла начиная с текущей позиции чтения/записи и длиною len байт если len > 0; c позиции раньше текущей на len и до текущей если len < 0; с текущей позиции до конца файла если len = 0.
Команды:
При программировании в shell можно воспользоваться командой
flock [options] <file|directory> <command> [command args]
Если отбросить подробности, то flock
пытается установить блокировку (вызовом flock()
, что почти очевидно) на указанный файл или каталог. Если блокировка уже установлена, то flock
уходит в "сон". Как только файл становится доступным, flock
блокирует его, выполняет указанную команду и завершается, освобождая блокировку.
В Linux и некоторых Unix возможна установка обязательных блокировок, которые приостановят вызовы read()
и write()
на заблокированных участках файла (документация по Linux). Для этого файловая система должна быть смонтирована с опцией-o mand, а в правах доступа к файлу должны быть одновременно выставлены флаг запрещения прав на выполнение группой и флаг смены группы процесса при выполнении SGID. В буквенной записи это выглядит так: -rw-r-Sr-- (если бы было право на исполнение группе, то была бы показана маленькая 's') .
write()
, open()
с флагом O_TRUNC и т.п.)read()
и вызовы, изменяющие содержимое файла (write()
и т.д.)файловый дескриптор ФД (созданный через вызов open
или унаследованный от родительского процесса), является целочисленным индексом в таблице ссылок на структуры открытых файлов процесса. Сама структура, связанная с открытым файлом, содержит дальнейшую ссылку на виртуальный Inode файла в памяти ядра, а так же флаги доступа к файлу (чтение, запись), текущую позицию чтения/записи в файле и некоторые дополнительные данные. Информация о блокировках используется совместно несколькими процессами, а потому вынесена в виртуальный Inode.
По соглашениям, принятым в ОС Unix, в момент запуска программы должны быть открыты ФД 0, 1 и 2, которые интерпретируются как STDIN, STDOUT и STDERR соответственно. Другие ФД так же могут быть открыты, но это никак не регламентируется. При выделении нового ФД при вызове open
, pipe
, dup
и т.п., выбирается наименьший свободный ФД.
Процесс, имея ФД, может создавать на его основе новые ФД, которые будут ссылками на ту же структуру данных в ядре, что и оригинальный ФД и соответственно те же флаги и позицию чтения/записи. Закрытие ФД уменьшает количество ссылок на открытый файл. Фактическое закрытие файла произойдёт тогда, когда на него не будет ссылаться ни один ФД.
Для создания ссылок используются вызовы dup
и dup2
. dup
возвращает первый свободный номер ФД, а dup2
позволяет явно указать номер нового ФД. Если желаемый номер ФД окажется занятым, то он сначала будет закрыт.
int newfd=dup(oldfd)
int newfd=dup2(oldfd,1)
Самое частое использование dup2
это перенаправление стандартного ввода-вывода. Например, shell, выполняя команду:
grep str infile > outfile
может выполнить следующую последовательность действий
Вариант 1:
if (!fork() ){
// Дочерний процесс для запуска grep
// Освобождаем ФД==1 (STDOUT)
close(1);
// open должен вернуть наименьший свободный ФД т.е. 1
open("outfile", O_CREAT|OTRUNC|O_WRONLY, 0544);
// Запускаем программу grep, наследующую STDOUT -> "outfile"
execlp("grep", "str", "infile",NULL);
}
Вариант 1 в многопоточной среде может привести к гонкам за захват ФД==1, поэтому рекомендуется Варинт2:
if (!fork() ){
// Дочерний процесс для запуска grep
// open возвращает какой-то ФД
int fd= open("outfile", O_CREAT|OTRUNC|O_WRONLY, 0544);
// Связываем 1 (STDOUT) с "outfile" через fd
dup2(fd,1);
// Освобождаем fd
close(fd);
// Запускаем программу grep, наследующую STDOUT -> "outfile"
execlp("grep", "str", "infile",NULL);
}
Перенаправление через pipe
выполняется аналогично.
Для создания каталога в вызов mkdir() передаётся путь к создаваемому каталогу. Вышележащий каталог должен существовать (??) и пользователь должен иметь право на запись в него.
#include <sys/stat.h>
int mkdir(const char *path, mode_t mode);
При создании файла вызовом open() с флагом O_CREAT в указанном в пути к файлу каталоге создаётся запись с именем файла. Новое имя для файла можно создать вызовом link().
#include <unistd.h>
int link("oldpath", "newpath");
Поскольку жёсткие ссылки возможны только в рамках одной ФС, то и имена "oldpath" и "newpath" должны находиться внутри одной ФС.
В Unix нет операции удаление файла. Есть лишь операция удаление из каталога жёсткой ссылки (имени) на объект. Каждый раз после удаления имени уменьшается счётчик имён в Inode файла. Когда счётчик имён становится равным нулю, файл становится недоступным по имени. Однако, если в этот момент файл был открыт одним или несколькими процессами, то он не удаляется из ФС. Только тогда, когда у файла ноль имён и он не открыт ни в одном процессе, его Inode и его блоки данных помечаются как свободные, т.е. происходит уничтожение файла.
Данное поведение позволяет выполнять трюк с невидимыми временными файлами. Файл создаётся, открывается, и тут же удаляется его имя. В результате невидимый файл будет существовать до завершения процесса в котором он был создан. В такой ситуации аварийное завершение процесса не оставляет нигде в ФС ненужных временных файлов.
Вызов unlink() неприменим к каталогам. unlink() символической ссылки удаляет имя символической ссылки, никак не влияя на объект, на который указывает символическая ссылка.
#include <unistd.h>
int unlink("file");
Вызов rename("old", "new") эквивалентен паре вызовов link("old", "new"); unlink("old");. В отличие от этой пары rename() можно применять к каталогам, а так же он не удалит объект при переименовании его в самого себя - rename("x", "x").
Каталог в Linux можно открыть как файл с помощью open с флагом O_DIRECTORY:
struct old_linux_dirent {
long d_ino; /* inode number */
off_t d_off; /* offset to this dirent */
unsigned short d_reclen; /* length of this d_name */
char d_name [NAME_MAX+1]; /* file name (null-terminated) */
} olddirp[SIZE];
// в Linux каталог можно открыть с помощью open
int fd=open("dirname", O_DIRECTORY);
// и прочитать его внутреннюю структуру
retval=readdir(fd, olddirp, SIZE);
но так делать не надо.
Для работы с каталогами надо использовать библиотечные функции opendir(3), readdir(3) и т.д.
#include <dirent.h>
struct dirent {
ino_t d_ino; /* номер inode */
off_t d_off; /* заглушка */
unsigned short d_reclen; /* заглушка */
unsigned char d_type; /* тип файла; поле не стандартизовано */
char d_name[256]; /* имя файла */
};
// открыть каталог
DIR *dirp;
dirp=opendir("dirname");
// или
dirp=fopendir(fd);
// прочитать запись за записью
struct dirent *rec;
do{
rec=readdir(dirp);
}while(rec)
// начать сначала
rewinddir(dirp);
// закрыть каталог
closedir(dirp);
Удалять можно только пустые каталоги.
#include <unistd.h>
int rmdir("pathname");
С каждым открытым файлом в UNIX связано понятие позиции чтения-записи. Это понятие не применимо к сокетам и каналам, но обычном файле у нас есть внутренняя нумерация байт, начинающаяся с нуля, и некая условная головка чтения-записи. Позиция головки одна для чтения и записи. При открытии файла на чтение или на запись головка выставляется на начало файла. При каждой операции чтения-записи головка устанавливается на позиции за последним считанным-записанным байтом. Особый случай - это открытие файла на дозапись с флагом O_APPEND. В этом случае каждая операция записи предварительно перемещает головку в конец файла. Конец файла - это позиция равная числу байт в файле. Если мы начнем запись в конец файла, то будем писать после последнего существующего байта. Если начнем читать, то прочитаем 0 байт, что является признаком конца файла.
Иногда появляется желание переместиться по файлу вперед или назад и начать читать или писать с некой определенной позиции. Для того чтобы управлять положением головки используется вызов lseek, который изменяет положение головки чтения-записи. lseek получает файловый дескриптор и два значения: целочисленное значение смещения и макрос whence, который описывает, откуда это смещение отсчитывается. В man странице написано, что использование слова whence нарушает правила английского языка, но сохраняется по историческим причинам.
off_t pos=lseek(int fd, off_t offset, int whence);
Параметр whence может принимать три значения: + SEEK_CUR - смещение относительно текущей позиции. Если смещение положительное, то головка смещается к концу файла, если отрицательное - то ближе к началу файла. + SEEK_END - смещение вычисляется относительно текущего размера файла. + SEEK_SET - смещение от начала файла. Отрицательное смещение не имеет смысла.
В качестве результата lseek возвращает текущую позицию относительно начала файла.
Смещение 0 от текущей позиции не меняет положение головки, но возвращает текущую позицию. Нулевое смещение относительно конца файлы вернет его текущий размер и переместит головку на байт, следующий за последним в файле. Слишком большое отрицательное смещение переводит головку в нулевую позицию.
Если один файловый дескриптор создан из другого с помощью dup() или dup2(), то положение их головок всегда совпадает. Если применить lseek() к таким файловым дескрипторам из разных потоков, то это может привести к состоянию гонок и неожиданному поведению.
У вызова lseek есть проблема, связанная с разрядностью чисел.
В 32 разрядных системах максимальное значение целочисленного смещения четыре гигабайта, в то время как все современные файловые системы поддерживают файлы большего размера. Проблема в том, что мы просто не можем записать значение смещение больше 4ГБ. Тем не менее выполняя последовательные вызовы lseek мы можем перемещаться неограниченно далеко.
В таком случае lseek возвращает -1 и выставляет переменную errno в значение EOVERFLOW.
Чтобы работать со сверхбольшими файлами в 32 разрядной системе компилятор gcc и библиотека gnulibc предоставляют специальную функцию lseek64, у которой в качестве параметра offset передается структура, состоящая из двух целочисленных значений. Для ее использования необходимо открывать файл с флагом O_LARGEFILE и указывать при компиляции особые макросы -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS.