LINUX.ORG.RU

Как все таки Ъ понять что fseek вылетел за пределы файла открытого на чтение?

 


0

1

В манах пишут что fseek может выходить за пределы файла, это типа нормально. Но мне бы таки очень хотелось понять что файл кончился - файл мог до конца не записаться.

Как это сделать Ъ? Попытаться прочитать маленький кусочек? Заранее определить размер файла через fstat? Какие еще варианты?

★★★★

Попытаться прочитать маленький кусочек?

зачем читать кусочек, если у файла есть размер?

alysnix ★★★
()

Вариантов несколько.

Во-первых, да, можно определить размер через fstat() и считать в память ровно столько, сколько можно (либо целиком файл, либо по кускам, но не более общей длины файла). Типа так:

struct stat buff;

...

printf("Size of the file is: %ld\n", buff.st_size);

Здесь buff.st_size это и есть искомая полная длина файла.

Во-вторых, можно просто провести операцию чтения из файла, когда указатель позиции находится в конце файла, и, если напоролись на флаг EOF, то дальше не читать. Типа так:

/**                                                                                                                                                    
 * Set file pointer to the given position of the given file.                                                                                                       
 *                                                                                                                                                     
 * - Returns 0 on success                                                                                                                              
 * - Returns -1 on EOF                                                                                                                                 
 * - Returns -2 if an error occured, see errno for error code                                                                                          
 * - Returns -3 if none of the above applies. This should never happen!                                                                                
 */                                                                                                                                                    

static int8_t set_fp_pos(FILE *f, uint64_t fp_pos)                                                                                                          
{                                                                                                                                                      
        int err = fseek(f, fp_pos, SEEK_SET);                                                                                                             

        if (err != 0) {                                                                                                                                
                if (feof(f) != 0) return -1;                                                                                                           
                if (ferror(f) != 0) return -2;                                                                                                         
                return -3;                                                                                                                             
        }                                                                                                                                              

        return 0;                                                                                                                                      
}

Нужно учитывать то, что fseek() сама по себе да, может выходить за пределы последней позиции в файле на тот случай, если будет необходимость записать что-то за пределы конца файла. Это такой извращённый способ добавления (дозаписи) данных в файл. Избавлять от такого поведения fseek() не нужно.

Третий вариант – отмапить (mmap()) содержимое файла в адресное пространство процесса. Тогда просто работаете с массивом символов в памяти. По временам это и лучше, т.к. если идёт активная обработка файлов разной длины, то после того как Вы сделаете munmap(), размеры потребляемой памяти уменьшатся. mmap() не раздувает кучу. Так же нужно учитывать что можно не весь файл сразу мапить, а по кускам. Т.е., в начале определяем можем ли отмапить сразу весь файл в память, и, если вдруг не можем, то работаем с файлом кусками. Вдобавок, при обработке файлов, может оказаться что этот вариант работает слегка быстрее чем случай, когда система борется с винчестером, пытаясь читать порции из файла.

В общем, разные варианты – надо от задачи смотреть.

Moisha_Liberman ★★
()
Последнее исправление: Moisha_Liberman (всего исправлений: 1)
Ответ на: комментарий от AntonI

А он и должен валиться.

Вы же пытаетесь прочесть данные за пределами выделенной памяти. Так что, чтение из mmap-ленного файла надо проводить строго не за пределами выделенного диапазона памяти. Проверяйте по ранее полученному через fstat() размеру и не выходите за границы, это ошибка.

Moisha_Liberman ★★
()
Ответ на: Вариантов несколько. от Moisha_Liberman

Третий вариант – отмапить (mmap()) содержимое файла в адресное пространство процесса.

Если что, fopen() под капотом вполне может mmap() задействовать. https://github.com/bminor/glibc/blob/595c22ecd8e87a27fd19270ed30fdbae9ad25426...

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)

Может если попросить или может сам промазать при указании SEEK_END? Если второе, то я в своей жизни видел десятки неправильных проверок длин в самых низкоуровневых утилитах.

t184256 ★★★★★
()
Ответ на: комментарий от AntonI

Да, как-то вот так:

#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[]){
	const char *which_file = argv[1];
	struct stat buff;

	/* Открываем файл на чтение и определяем его размер. */
	int fd = open(which_file, O_RDONLY);
	if(fd < 0) {
        	fprintf(stderr, "Can't open \"%s \"\n", which_file);
		exit(1);
	}
	int err = fstat(fd, &buff);
	if(err < 0) {
        	fprintf(stderr,"Can't open\"%s \"\n", which_file);
        	exit(2);
	}

	/* Мапим файл с уже понятным размером. */
	char *ptr = mmap(NULL, buff.st_size, PROT_READ,
			 MAP_SHARED, fd, 0);
	if(ptr == MAP_FAILED) {
        	fprintf(stderr, "Mapping failed\n");
		exit(3);
	} 
	close(fd); /* Файл считан, fd закрыли, больше не нужен */

        /* Выводим на экран ранее отмапленное. */
	ssize_t n = write(STDOUT_FILENO, ptr, buff.st_size);
	if(n != buff.st_size){
        	fprintf(stderr, "%s\n", "Write failed");
	}
        /* На таком коротком примере этого можно не делать, т.к. 
         * при выходе из приложения система за нами всё зачистит,
         * но пусть будет чисто иллюстрации ради.
         */
	err = munmap(ptr, buff.st_size);
	if(err != 0){
        	fprintf(stderr, "%s\n", "Unmapping failed");
        	exit(4);
	}
    return 0;
}

Я тут выставил всякие exit() с разными кодами выхода чтобы в шелл-скрипте можно было бы посмотреть состояние и выяснить где именно отвалилось и на каком этапе. Можно игнорировать или просто возвращать ошибку через return -1, это не важно в итоге.

В общем, примерно как-то так. Ну и вывод на экран содержимого файла это демонстрация как не вываливаться за пределы отмапленного файла.

Удачи.

Спасибо!

Не за что. =)

Moisha_Liberman ★★
()
Последнее исправление: Moisha_Liberman (всего исправлений: 3)
Ответ на: комментарий от t184256

Ошибки это.

Если второе, то я в своей жизни видел десятки неправильных проверок длин в самых низкоуровневых утилитах.

Править такое надо, да. =)

Moisha_Liberman ★★
()
Ответ на: комментарий от t184256

Не, он там реально мажет. Файл битый приходит, часть данных в хвосте не записалось (уж не знаю почему, не я его генерю) а голова с размерами и пр. нормальная. Ну и дальше если не проверять - mmap и сегфолт.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Хммм...

Тогда да, надо сравнивать результаты fstat() и то, что должно быть по длине файла из заголовка и выкидывать либо предупреждение, либо в лог какой писать что должно типа было прийти столько байт, а реально пришло столько-то, что мог, то обработал, идите на фиг, дорогие товарищи. Ну, вот как-то вот так. Чтоб вопросов не было где данные и почему не всё в базу обмолотилось, например.

Ну и, само собой, дату-время, имя файла, который обмолачивали. И пусть на стороне генерации разбираются и сетевики.

Moisha_Liberman ★★
()
Ответ на: комментарий от AntonI

А вот это уже странно.

А вот да блин, я же (ptr == MAP_FAILED) проверял, и он с неправильными размерами спокойно мапился…

Потому что если fstat() возвращает некую длину файла, то по идее, для ОС этот файл и имеет нужную длину. Но в файле, который обрабатываем, данные не до конца записались. А можно попробовать залить такой файл и сделать что-нибудь типа fsync? Возможно что в приложении, которе пишет файлы в Вашу систему, в буферах что-то остаётся и получается что файл вроде есть, длина вроде корректна, а на деле из буфера записи данные ещё не все высыпались на диск.

Moisha_Liberman ★★
()
Ответ на: Хммм... от Moisha_Liberman

Зачем вообще делать fstat, если можно делать fseek относительно конца файла? То есть хочешь с конца файл читать - переходи в позицию относительно конца файла, а не вызывай fstat, чтобы ограничивать максимальную позицию, задаваемую fseek.

xmikex ★★★★
()
Ответ на: комментарий от xmikex

Можно.

Но насколько я понял задачу ТС, ему как раз не ясно чё там с концом файла вообще есть. И тут хрен его знает что получится в итоге может.

Moisha_Liberman ★★
()
Ответ на: Хммм... от Moisha_Liberman

У нас все проще;-) Это вьювер, форматов зоопарк, где то что то отвалилось-не записалось, бывает. Не смог открыть - ругнулся - пошел дальше.

Для меня просто был сюрприз что fseek спокойно за пределы файлы ходит и всем пофик. Поправил конечно в своей обертке для потоков ввода/вывода что б ругалося, для меня это экзотика.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Ну сделал fseek в конец и узнал его позицию. У fseek есть параметр, который дает задавать позицию относительно конца.

xmikex ★★★★
()
Ответ на: комментарий от xmikex

О, спасибо. Это третий вариант.

AntonI ★★★★
() автор топика

В манах пишут что fseek может выходить за пределы файла, это типа нормально.

А можно цитату из манов?

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

Явно про такое написано в мане на lseek() не fseek()

lseek() allows the file offset to be set beyond the end of the file (but this does not change the size of the file). If data is later written at this point, subsequent reads of the data in the gap (a «hole») return null bytes ('\0') until data is actually written into the gap.

pathfinder ★★★★
()
Последнее исправление: pathfinder (всего исправлений: 1)
Ответ на: комментарий от pathfinder

Маны манами, но можно же палочкой потыкать:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char **argv){
	FILE* f = fopen(argv[1], "r");
	printf("seek: %i, ", fseek(f, atoi(argv[2]), 0));
	printf("tell: %i, ", int(ftell(f)));
	printf("eof: %i\n", feof(f));
	return 0;
}
$ ./seek-test ~/.bashrc 100000
seek: 0, tell: 100000, eof: 0
AntonI ★★★★
() автор топика
Ответ на: комментарий от pathfinder

;-)

Я просто вчерась выкатил пререлиз клиент-серверной софтины. запускаю на каких то первых попавшихся данных на сервере - все ок, потом улетает с сегфолтом. Я в панике, что за хрень, начал копать… вот такая выяснилась фигатень, главное что я про такое поведение fseek не знал до сегодняшнего утра;-(

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Эта да, всего знать невозможно. Никто не застрахован.

pathfinder ★★★★
()
Ответ на: комментарий от AntonI

Я в панике, что за хрень, начал копать…

у вас еще бага где-то есть. Чтобы файлы сами по себе обрезались - такого мир давненько не видел, если б такое было - все давно б упало.

что-то у вас там с длинами. а вариантов много - либо две стороны интерпретируют длину по разному, либо пишут ее читают в разном порядке байт/слов, может там знаковые длины, может за длину приняли вовсе другое поле данных.

по любому, несоответствие записанной длины и физической длины файла - это сюрреализм, должно быть расследовано, виновные наказаны.

alysnix ★★★
()
Ответ на: Вариантов несколько. от Moisha_Liberman

Т.е., в начале определяем можем ли отмапить сразу весь файл в память, и, если вдруг не можем, то работаем с файлом кусками.

Не сможем разве что на 32-битных системах. Или при наличии лимитов на виртуальную память. В извращённых случаях, короче.

Sorcerer ★★★★★
()
Последнее исправление: Sorcerer (всего исправлений: 1)
Ответ на: комментарий от alysnix

у вас еще бага где-то есть.

Эта точно не моя. В софте который эти файлы генерит я ни строчки не написал.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI
fseek(f,0,SEEK_END);
long len = ftell(f);
fseek(f,0,SEEK_SET);

И всё. Узнаёшь доступный размер и дальше не суёшься. Смещения должны быть контролируемые в пределах границ, а не от балды =)

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от Sorcerer

Можно и на 64 битах.

Не сможем разве что на 32-битных системах. Или при наличии лимитов на виртуальную память. В извращённых случаях, короче.

Код выше работает на 64 битах. Там дело в размерности второго аргумента вызова fseek(), т.е., смещения. Сейчас по ману у меня long offset. Т.е., для 64 битной системы это будет 8 байт, на 32х разрядах это будет всё тот же long, который там составляет 4 байта. НУ, собббстна, оно и очевидно.

А можем мы отмапить весь файл или нет, это зависит от доступного объёма RAM и размера файла, который хотим мапить.

Moisha_Liberman ★★
()
Ответ на: комментарий от alysnix

Такое может быть.

по любому, несоответствие записанной длины и физической длины файла - это сюрреализм, должно быть расследовано, виновные наказаны.

У меня был такой случай. С Solaris прилетали данные. Файлы, примерно по 120К размером (плюс-минус). CDR (call detail records). Данные выгребались на линукс по сети и мой демон, надзиравший за каталогом через dnotify (inotify тогда только-только входил в моду), получал сигнал об изменении файловой системы, дескать, ФС изменилась, ступай глянь чё там. Но так как это был high-load, возникала забавная проблема. Проходил временной лаг между тем, как файл был fclose(fd); и тем, как ОС сбрасывала остатки данных в него. Т.е., мой демон уже начинал читать данные из файла, мапить его, а конец файла ещё не был записан. Хотя, размер уже был вроде как выставлен корректно.

Поэтому я и написал ТС вот это – Как все таки Ъ понять что fseek вылетел за пределы файла открытого на чтение? (комментарий)

Преодолевается сравнительно просто (для начала, без долгих исправлений в приложении, где мы что-то пишем на диск, то чо потом ТС читает своим приложением):

/* Просто добавляем fsync() */
fsync(fd);
fclose(fd);

Да, это несколько замедляет работу, без fsync() оно вроде как поживее, но тут уже… Ну и ненужно использовать fdatasync(), т.к. при использовании этой функции не будет изменяться метаинформация о файле, а нам тот же file size всё-таки нужен. Не будем рисковать. Чуть дольше, но надёжнее.

Если с более долгими переписываниями приложения, которое пишет файлы на диск, то тогда делаем так (как и переписали под моим чутким руководством для перехода на Linux):

/* Важно! */
#define _GNU_SOURCE

...

/* Чтобы не отлавливать EINTR при прерывании потока исполнения.
 * При записи может быть переключён контекст и, по идее, надо 
 * проверить errno и повторить обращение, макрос
 * TEMP_FAILURE_RETRY берёт на себя эту работу.
 */
nbytes = TEMP_FAILURE_RETRY(write(file_desriptor,
                            buffer_to_write,
                            count_bytes_to_write));
/* Вроде, как и fsync(), но нет. */
sync(file_descriptor);
close(fd);

Тут дело в том, что ф-ии вида fwrite()/fprintf(), это функции как правило с буферизацией на приложения. Ф-ии типа write это функции с буферизацией на уровне ОС, поэтому sync() здесь работает с буфером самой ОС, а не приложения. write() как правило медленнее, т.к. это всё таки сисколл, но зато надёжность записи на диск не просто железная, она там железо-бетонная. Вдобавок, write() это атомарная ф-я, что из сети мы ранее прочитали, именно то мы на диск и записали.

Здесь, конечно смотреть надо именно ту сторону, которая пишет на диск файлы, ТС тут ни при чём.

Moisha_Liberman ★★
()
Ответ на: Можно и на 64 битах. от Moisha_Liberman

А можем мы отмапить весь файл или нет, это зависит от доступного объёма RAM и размера файла, который хотим мапить

Нет такой зависимости. Не думаете же вы, что mmap копирует весь файл в оперативку? mmap вообще не так работает. Он только устанавливает маппинг. Далее куски файла подгружаются в оперативку постранично по мере обращения к замапленным адресам. Эти страницы сидят в кеше и могут быть в любой момент вытеснены из памяти при необходимости, а если к ним опять обратятся, то они снова подгрузятся из файла.

Sorcerer ★★★★★
()
Ответ на: Можно и на 64 битах. от Moisha_Liberman

Там дело в размерности второго аргумента вызова fseek(), т.е., смещения. Сейчас по ману у меня long offset.

man fseeko
man ftello

Не благодарите ;)

bugfixer ★★★★★
()
Ответ на: Можно и на 64 битах. от Moisha_Liberman

А можем мы отмапить весь файл или нет, это зависит от доступного объёма RAM и размера файла, который хотим мапить.

Небольшая поправочка: не «доступного объёма RAM», а «доступного virtual address space» - отмапить целиком 4Gb+ файл (реальный предел существенно ниже) в 32-битной апликухе не удастся ни при каких раскладах, даже если операционка 64-битная и физической памяти завались.

bugfixer ★★★★★
()
Ответ на: комментарий от Sorcerer

Нет.

Речь вообще не про то. Вы не поняли.

Нет такой зависимости. Не думаете же вы, что mmap копирует весь файл в оперативку? mmap вообще не так работает. Он только устанавливает маппинг. Далее куски файла подгружаются в оперативку постранично по мере обращения к замапленным адресам. Эти страницы сидят в кеше и могут быть в любой момент вытеснены из памяти при необходимости, а если к ним опять обратятся, то они снова подгрузятся из файла.

На цифрах. У Вас есть сервер. На нём 64Gb RAM физической памяти. У Вас есть файл, который надо обработать. Размер – 300Gb. Можно и больше, не важно, цифры для примера.

Что нужно сделать – в рантайме понять сколько можно забрать себе памяти под mmap() чтобы не отправить систему в свап и чтобы не увалить её, и далее кусками работать с файлом. Потому как если Вы «пролюбите» вспышку, то либо у Вас система просто начнёт жёстко свапиться, либо откажется работать, просто вернув из вызова mmap() ошибку типа «не могу зарезервировать память».

Чтобы посчитать правильно нужный объём, подгружаемы из файла, смотрите на доступность физической памяти, т.е., RAM и определяете сколько страниц Вы можете забрать безнаказанно для своих нужд (вычисляется как N * sysconf(_SC_PAGE_SIZE)) и просто двигаете offset.

Если размер файла, который Вы пытаетесь отмапить сопоставим с размером доступной памяти (физической, а не виртуальной, т.е. именно и чисто RAM), то даже задумываться не о чем. Просто мапите, да и всё. В мане хороший пример, там всё написано в коде.

Про страничную организацию в мане так же написано, надеюсь, их все читают.

Moisha_Liberman ★★
()
Ответ на: комментарий от bugfixer

Блин... Вот уже не знаю — ржать или материться. =)))

man fseeko man ftello

Не благодарите ;)

Ну и нафига мне ещё телодвижения делать, если с ммапнутым файлом всё проще? =)))

Moisha_Liberman ★★
()
Ответ на: комментарий от bugfixer

А вот за это в high-load расстреливают.

Я серьёзно. =)))

Небольшая поправочка: не «доступного объёма RAM», а «доступного virtual address space» - отмапить целиком 4Gb+ файл (реальный предел существенно ниже) в 32-битной апликухе не удастся ни при каких раскладах, даже если операционка 64-битная и физической памяти завались.

В принципе, Вы конечно правы. Но только теоретически. Всегда для high-load нужно чётко понимать чем мы рискуем. Оно, правда, и не для high-load было бы хорошо, конечно, но тут уж не до жиру, быть бы живу.

Если мы переходим в virtual address space, то мы подразумеваем что у нас есть объём RAM + объём swap. Всё верно. Но если мы начинаем свапиться на больших файлах, то это уже плохо. Если мы начинаем свапиться на больших файлах и, вдобавок ещё и часто (хуже может быть только «постоянно»), то всё совсем плохо. Производительность системы будет в районе ни х… «никакая». Поэтому, тут уже надо смотреть сколько памяти у нас есть в распоряжении RAM и именно в рантайме, в момент исполнения нашего кода. Ну и прикидывать нагрузку на систему хотя бы примерно. Чтобы получить более-менее удовлетворительный по производительности результат. Потому что если система уйдёт в свап но уже не из-за Вашего кода, а из-за моего, то какая на фиг разница для заказчика почему система ушла в свап и еле-еле ногой дрыгает… =)))

P.S. Был такой ныне забытый термин robust programming. Ну вот. Оно и есть. =)

Moisha_Liberman ★★
()
Последнее исправление: Moisha_Liberman (всего исправлений: 3)
Ответ на: Нет. от Moisha_Liberman

Речь вообще не про то. Вы не поняли.

Речь как раз про то. Это вы не поняли. Вы спокойно можете смапить файл превышающий объем вашей оперативки. И работать с ним. И ничего не будет свопиться. Запутались вы, короче. С анонимной памятью перепутали, наверное.

Sorcerer ★★★★★
()
Ответ на: Такое может быть. от Moisha_Liberman

Ф-ии типа write это функции с буферизацией на уровне ОС, поэтому sync() здесь работает с буфером самой ОС, а не приложения.

fflush() + fsync() не? Сисколы лучше вообще без нужды не трогать.

untitl3d
()
Ответ на: комментарий от untitl3d

fflush() + fsync() не? Сисколы лучше вообще без нужды не трогать.

Очень здравая мысль, плюсую. Но fsync() нужен только если критично чтобы данные хитнули «the platters». Если от падения машинки защищаться не нужно, то после fflush() все и так последние изменения увидят.

bugfixer ★★★★★
()
Ответ на: Блин... Вот уже не знаю — ржать или материться. =))) от Moisha_Liberman

man fseeko man ftello

Не благодарите ;)

Ну и нафига мне ещё телодвижения делать, если с ммапнутым файлом всё проще? =)))

Выдернуто из контекста. Это было про portability между 32 и 64 бит. Кто хотел - тот услышал.

bugfixer ★★★★★
()
Ответ на: А вот за это в high-load расстреливают. от Moisha_Liberman

В принципе, Вы конечно правы. Но только теоретически.

Очень даже практически. Хочу увидеть successful mmap() на 4Gb+ из 32 битной апликухи. Даже на idle машинке с RAM гигов этак 192.

Всегда для high-load нужно чётко понимать чем мы рискуем.

Безусловно. Кому-то лишние микросекунды стоят много-много денюжков.

Потому что если система уйдёт в свап

Я тут уже где-то говорил - по моему глубокому убеждению swap нужен только для того чтобы обмануть overcommit. Если Вы допускаете actual swapping на боевых машинках - Вы что-то совсем неправильно делаете.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.