Автор Тема: Малък въпрос за lseek()  (Прочетена 7444 пъти)

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Малък въпрос за lseek()
« -: Aug 28, 2012, 17:20 »
Здравейте,

Отдавна не съм пускал тема, но днес намерих нещо интересно.
 Та, ровя се в лекциите на Мария ( по досадни за мен причини ) и стигам до секцята със задачки за lseek(). Решавам да напиша следния код,  ( ядрото на skynet ):


Код
GeSHi (C):
  1. #include <fcntl.h>
  2. #include <unistd.h>
  3.  
  4. int main(){
  5.  
  6. int fd = open("file",O_WRONLY|O_TRUNC|O_CREAT);
  7. if (fd < 0 ){
  8.        write(2,"Cannot open file\n",17);
  9.        return 1;
  10. }
  11. write(fd,"abcde",5);
  12. off_t off = lseek(fd,-10,SEEK_SET);
  13. if ( off < 0 ){
  14.        write(2,"lseek returned error!\n",22);
  15.        perror("");
  16. }
  17. close(fd);
  18. return 0;
  19. }

Целта на кода е да тества какво ще се получи ако имаме отрицателен offset. Тривиалната част на теста мина ОК
 - не може да има зададен отрицателен offset от началото на файла и lseek() връща -1  като задава стойност EINVAL на errno...

Изпълнението на програмката води до:
Код
GeSHi (Bash):
  1. $ ./zad10.exe
  2. lseek returned error!
  3. Invalid argument

НО

http://pubs.opengroup.org/onlinepubs/009695399/functions/lseek.html

Цитат
The POSIX.1-1990 standard did not specifically prohibit lseek() from returning a negative offset. Therefore, an application was required to clear errno prior to the call and check errno upon return to determine whether a return value of ( off_t)-1 is a negative offset or an indication of an error condition. The standard developers did not wish to require this action on the part of a conforming application, and chose to require that errno be set to [EINVAL] when the resulting file offset would be negative for a regular file, block special file, or directory.

Интересно. Нека сега да коментираме #include <unistd.h>. . Простия ламър, като мен, би си помислил, че този код няма да се компилира, но това разбира се не така... lseek e системен примитив и gcc не би трябвало да се интересува от някакъв специален хедър.

Нека сега да премахнем #include <unistd.h> и да компилираме наново. Грешка уж няма, но:

Код
GeSHi (Bash):
  1. $ ./zad10.exe
  2. lseek returned error!
  3. Success

Т.е  lseek връща число по-малко от 0. -10 , ако трябва да сме точни - отрицателен offset, а и за капак  не задава errno.  Ок. Нека да видим асемблерния код:

Код
GeSHi (Bash):
  1. $ diff zad10.s.included zad10.s.excluded
  2. [ivan@ivan-laptop System Programing]$
  3.  
  4. -rwxrwxr-x. 1 ivan ivan 7182 Aug 28 16:49 zad10.exe.excluded
  5. -rwxrwxr-x. 1 ivan ivan 7150 Aug 28 16:53 zad10.exe.included
  6.  
  7.        movl    -4(%rbp), %eax
  8.        movl    $1, %edx
  9.        movl    $-10, %esi
  10.        movl    %eax, %edi
  11.        movl    $0, %eax
  12.        call    lseek
  13.  
  14.  

Очевадно става въпрос, за това, че когато няма include на <unistd.h> gcc използва друга дефиниция на пустата функция, защото размера на файловете е различен (имам предвид, че явно call lseek има друго значение при свързването).

Моля ви някой да ми обясни къде бъркам?




 
« Последна редакция: Aug 28, 2012, 18:55 от shoshon »
Активен

gat3way

  • Напреднали
  • *****
  • Публикации: 6050
  • Relentless troll
    • Профил
    • WWW
Re: Малък въпрос за lseek()
« Отговор #1 -: Aug 29, 2012, 02:05 »
Не мога да репродуцирам твоя резултат, но знам каква е причината за това. Няма нищо общо с lseek().

Примерът ти не е коректен. errno съдържа кода на грешка от последната изпълнена libc функция. В случаят обаче това не е lseek(), а write(), защото ти го ползваш за да ти изпише "lseek returned an error" и това наистина минава без драми

Има един проблем с това - POSIX стандарта не специфицира изрично какво трябва да се случи ако функцията не връща грешка. Това е въпрос на имплементация. Тоест, може да си дефинира код за "success", но може просто да не промени стойността на errno. Подозирам че при твоята системна C библиотека става нещо такова, включването на unistd.h променя поведението поради някаква причина. Не мога да кажа дали това е правилно или грешно, понеже пак казвам, стандартът не покрива такива случаи. Поради тази причина, ако трябва да проверяваш errno, прави го _веднага_ след функцията, иначе не е сигурно какво ще има там. В случаят дори един write() е напълно достатъчен да ти счупи нещата.
Активен

"Knowledge is power" - France is Bacon

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #2 -: Aug 29, 2012, 02:46 »
Мерси за помощта!

Истината е, размяната на write и perror не променя резултата. В wiki пише, че:

In the C and C++ programming languages, unistd.h is the name of the header file that provides access to the POSIX operating system API. It is defined by the POSIX.1 standard, the base of the Single Unix Specification, and should therefore be available in any conforming (or quasi-conforming) operating system/compiler (all official versions of Unix, including Mac OS X, Linux, etc.

Тук смисъла на API някак ми убягва. Не е ли работа на компилатора да разбира кои функции са примитиви и да записва съответните прекъсвания?

И аз имам теория btw, но наистина ме е срам да я споделя :)

Вече не мисля, че въпросът ми е толкова важен - прост пример до къде стига човек заради един пропуснат include...
« Последна редакция: Aug 29, 2012, 02:48 от shoshon »
Активен

appmaster

  • Новаци
  • *
  • Публикации: 2
    • Профил
Re: Малък въпрос за lseek()
« Отговор #3 -: Aug 29, 2012, 03:14 »
Я сподели каква ти е торията, че и на мен ми стана интересно.
Според мен има нещо общо това, че компилатора ти ползва едни ресурси, а операционнат ти е кеширала някакви други/стари библиотеки и при деклариране на include-a ти сменя версиите може би...

Странна история.
Активен

gat3way

  • Напреднали
  • *****
  • Публикации: 6050
  • Relentless troll
    • Профил
    • WWW
Re: Малък въпрос за lseek()
« Отговор #4 -: Aug 29, 2012, 03:20 »
А, това вече е много странно. Така както го разбирам, unistd.h ти гарантира (доколкото може да се гарантира) POSIX-подобно поведение. В противен случай, функциите от системната библиотека може да имат implementation-specific поведение. Как става това - примерно с макроси. Ако включиш unistd.h, тогава се #define-ват разни неща, спрямо които имаш така да го наречем "POSIX" поведение.
Активен

"Knowledge is power" - France is Bacon

remotex

  • Напреднали
  • *****
  • Публикации: 344
    • Профил
Re: Малък въпрос за lseek()
« Отговор #5 -: Aug 29, 2012, 18:00 »
По случайност да си с 64 битова ОС?
Моето предположение е... (преобразуването на типовете е различно с/без макросите от unistd.h)
Я пробвай с това - при мен даде очаквания резултат

off_t offset = -10;
off_t off = lseek(fd, offset , SEEK_SET);

Без горното ми даваше следното
с unistd.h
errno=22
EINVAL
Invalid argument
lseek returned error!

без unistd.h
errno=0
Success
lseek returned error! - това само защото върнатия офсет е отрицателен НО никъде не проверявате ст-та на errno!

Добра идея е да се проверява errno все-пак; даже хората препоръчват да се му присвоява 0 преди извикване на функция и веднага след това ст-та да се съхранява в локална променлива (и е малко по-сложно при многонишкови приложения щото е volatile)

П.П. Другата ми (по-тъпата) идея беше за Selinux, но след кратка проверка установих че е горното поне на мойта машина
Fedora x86_64

NB! Уф - Една бърза проверка оналйн щеше да ми спести половин час експерименти...
П.П.П. Много стар бъг - януари 1995 г. - http://web.archiveorange.com/archive/v/nfJUhVgv6RvEuqi9NzXB
Хехех - Важното е да не настъпваме едни и също мотики повече от веднъж

SYNOPSIS
     #include <unistd.h>     off_t
     lseek(int fildes, off_t offset, int whence)

It is an off_t so 64 bits.

and I'd recommand to use a cast (off_t) 100 instead of just 100 but that
may not be necessary if <unistd.h> is included.
« Последна редакция: Aug 29, 2012, 18:10 от remotex »
Активен

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #6 -: Aug 29, 2012, 19:02 »
@remotex точно това е проблема errno си е 0 без да го пипам. И аз си мислех за битовете :D

Здравейте пак,

За да не ми умре темичката, а и за да  дам последен шанс на bob_bob_mara да се изяви, ето моята мисла:

Включването на unistd.h яно е важно за да знае компилатора капабилитито ( български превод :( ) на систмата. Пример:
Ако компилирам със включен unistd.h, lseek се извиква със следните инструкции:


  400638:       8b 45 fc                mov    -0x4(%rbp),%eax
  40063b:       ba 01 00 00 00          mov    $0x1,%edx
  400640:       48 c7 c6 f6 ff ff ff    mov    $0xfffffffffffffff6,%rsi <- 64-битов регистър
  400647:       89 c7                   mov    %eax,%edi
  400649:       e8 62 fe ff ff          callq  4004b0 <lseek@plt>


А ако го направя без unistd.h се извиква така ( дори и да компилирам с gcc -m64 ):

  400642:       8b 45 fc                mov    -0x4(%rbp),%eax
  400645:       ba 01 00 00 00          mov    $0x1,%edx
 40064a:       be f6 ff ff ff          mov    $0xfffffff6,%esi <- 32битов код 
  40064f:       89 c7                   mov    %eax,%edi
  400651:       b8 00 00 00 00          mov    $0x0,%eax
  400656:       e8 55 fe ff ff          callq  4004b0 <lseek@plt>


В кода на ядрото четем:
http://lxr.free-electrons.com/source/fs/ext4/file.c#L218
http://lxr.free-electrons.com/source/fs/read_write.c#L69
http://lxr.free-electrons.com/source/fs/read_write.c#L38

Забележете, че е точно както са написали в open group - няма проверка дали offset е отрицателен от началото на файла - ако тази заявка е невалидна, то следва върната стойност да мине през glibc и да се зададе стойност на errno=-върната стойност. Т.е. най-вероятно, ако offset-а е невалиден, то glibc вдига errno.


Нека сега да разширим задачата. Долу е кода на skynet2 :

Код
GeSHi (C):
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <errno.h>
  4. //#include <unistd.h>
  5.  
  6. #define lseek lseek64
  7.  
  8. int main(){
  9. int errno_tmp;
  10. int fd = open("file",O_WRONLY|O_TRUNC|O_CREAT);
  11. if (fd < 0 ){
  12.        write(2,"Cannot open file\n",17);
  13.        return 1;
  14. }
  15. write(fd,"abcde",5);
  16. off_t off = lseek(fd,-10,SEEK_CUR);
  17. errno_tmp = errno;
  18. if ( off < 0 ){
  19.        printf("Offset is currently negative: %d !\n",off);
  20.        printf("Errno=%d\n",errno_tmp);
  21.  
  22. }
  23.  
  24. int written_bytes = write(fd,"12345",5);
  25. errno_tmp = errno;
  26.  
  27. if (written_bytes < 0){
  28.        printf("Written bytes is negative. errno=%d\n",errno_tmp);
  29. }
  30. printf("Written bytes = %d!\n",written_bytes);
  31. close(fd);
  32.  
  33. return 0;
  34.  
  35. }
  36.  
  37.  

Компилираме и изпълняваме. Хайде да отгатнем какво има във file?

Код
GeSHi (Bash):
  1. $ ./zad10.exe
  2. Offset is currently negative: -5!
  3. Errno=0
  4. Written bytes = 5!
  5.  
  6.  
  7. $ cat file
  8. abcde
  9.  
  10.  
  11. ^C
  12. ### Точно така - cat няма да спре да работи...
  13.  
  14. $ ll file
  15. -rw-r--r--. 1 ivan ivan 4294967296 Aug 29 18:54 file
  16.  
  17. ### WoW 2^32 размер
  18.  
  19. $ du file
  20. 8       file
  21.  
  22. ### Или не?
  23.  


А къде точно е "12345" :) Интересно е как може да разцъкаме тази ситуация...
« Последна редакция: Aug 29, 2012, 19:21 от shoshon »
Активен

remotex

  • Напреднали
  • *****
  • Публикации: 344
    • Профил
Re: Малък въпрос за lseek()
« Отговор #7 -: Aug 29, 2012, 21:01 »
Бас държа че пак е поради същата причина която изтъкнах по-горе то точно затова и не работи заради неявното конвертиране от (по подразбиране литералите са) int който си е 32 бита към off_t който е 32 или 64 и понеже предполагам машината ти е 64 битова т.е. със и без -m64 все като 64 бита ти го компилира затова няма разлика все гърми - за да не ти гърми я го компилирай като 32 бит там няма да има конверсия от подразбиращия се тип за литералите който е int т.е. 32 бит към 32 битов off_t или както и преди казах добра практика е литералите да се указват със явен cast или суфикс който указва типа инак следва неявно конвертиране по подразбиране

Само информативно може ли да дадеш
изхода от следния пример при теб какво връща със и без unistd.h

Код
GeSHi (C):
  1. //sizes.c
  2. #include <stdio.h>
  3. #include <sys/types.h>
  4.  
  5. int main(void) {
  6.  off_t blah;
  7.  
  8.  return 0;
  9. }

Код
GeSHi (Bash):
  1. $ gcc -E sizes.c  | grep __off_t
  2. typedef long int __off_t;

//Добавка: А също така кажи и sizeof(ptrdiff_t) какво връща
Добавка: stddef.h дефинира size_t, ptrdiff_t и пр. няма значение кое - просто за сигурност да се убедим че кода е 32 или 64 битов - т.е. ще върне 4 или 8 байта размер съответно.
« Последна редакция: Aug 29, 2012, 21:24 от remotex »
Активен

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #8 -: Aug 29, 2012, 21:07 »
Без:
$ gcc -E sizes.c | grep __off_t
typedef long int __off_t;
  __off_t __pos;
  __off_t _old_offset;
typedef __off_t off_t;
extern int fseeko (FILE *__stream, __off_t __off, int __whence);
extern __off_t ftello (FILE *__stream) ;

Със:
$ gcc -E sizes.c | grep __off_t
typedef long int __off_t;
  __off_t __pos;
  __off_t _old_offset;
typedef __off_t off_t;
extern int fseeko (FILE *__stream, __off_t __off, int __whence);
extern __off_t ftello (FILE *__stream) ;
extern __off_t lseek (int __fd, __off_t __offset, int __whence) __attribute__ ((__nothrow__));
        __off_t __offset) ;
         __off_t __offset) ;
extern int truncate (__const char *__file, __off_t __length)
extern int ftruncate (int __fd, __off_t __length) __attribute__ ((__nothrow__)) ;
extern int lockf (int __fd, int __cmd, __off_t __len) ;


Добавка:

Това prtdiff_t не го знам къде е. Аз много не разбирам от C...
« Последна редакция: Aug 29, 2012, 21:20 от shoshon »
Активен

remotex

  • Напреднали
  • *****
  • Публикации: 344
    • Профил
Re: Малък въпрос за lseek()
« Отговор #9 -: Aug 29, 2012, 21:13 »
точно така long int и бас държа че под уиндоус това работи без проблем щото те ползват LLP64/IL32P64 модела при който int и long int са 32 бит докато линукс ползва LP64/I32LP64 където int е 32 а long int 64 бита

т.е. при теб long int или off_t на 64 битова машина са 64 бит, докато гол литерал като -10 е по-подразбиране int т.е. винаги 32 бит. Затова off_t(-10) го оправя също както и -10L
Пробвай само за експеримента да го компилираш като 32 (НЕ като 64) тогава всичко ще си е int и 32 бита и НП и това би трябвало да оправи нещата ..и публикувай резултата :-)

Справка  (секцията 64-bit data models)
http://en.wikipedia.org/wiki/64-bit_computing

Полезни четива по темата 32/64 бита (особено последното) - за тези които знаят английски (нищо че автора е руснак)
http://www.codeproject.com/KB/architecture/20ISSUES64BIT.aspx?msg=3485399
http://www.viva64.com/en/l/
http://www.viva64.com/en/a/0043/
« Последна редакция: Aug 29, 2012, 21:39 от remotex »
Активен

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #10 -: Aug 29, 2012, 21:26 »
Thanks,

За сега ще лягам и ше разгелдам утре пак какво ще отркия :)
« Последна редакция: Aug 29, 2012, 21:46 от shoshon »
Активен

remotex

  • Напреднали
  • *****
  • Публикации: 344
    • Профил
Re: Малък въпрос за lseek()
« Отговор #11 -: Aug 29, 2012, 21:53 »
Нещо прикаченият ти файл е празен

Последно уточняване от моя страна
0xFFFF FFF6 == -10 (signed int)
0xFFFF FFF6 == 4,294,967,286 (unsigned int) което е неправилно - би трябвало да се размножи знаковия бит "1" и да е 0xFFFF FFFF FFFF FFF6
4,294,967,286 + 10 (от "abcde" + "12345") = 4,294,967,296 точно колкото ти е размера на файла
дето cat нямаше търпение да го дочакаш
я пусни и tail на тоя файл - може би ще видим там на края и 12345 ;-)
Може да са там а може и да не са - ако ги е записало на отрицателно отместване тогава за да видим нещо в края запиши низ по-дълъг от 10 символа втория път :-)

П.П. Сама по себе си lseek не променя размера на файла а последващия запис на "12345" т.е. все трябва да ги е записало някъде та просто от любопитсво питам опашката на файла какво съдържа.
« Последна редакция: Aug 29, 2012, 22:15 от remotex »
Активен

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #12 -: Aug 29, 2012, 22:11 »
Така е, по късно се сетих и проверих. Явно файла е с дупка :)

Тая дупка смята ли се за заето пространство?

Щото аз толкова място на диска нямам :). Сещам се че и lastlog файла има подобно поведение като винаги показва максимален възможен размер.

Защо и как?

du показва 8 байта.
аз бях написал 10.
защо след като направя такъв счупен файл, флаговете на файла се сменят при всяко пускане на програмата?

Прикачения файл е празен да :)

Много интересно - ако пробваш да прикачиш /dev/zero и да го upload-неш (смех!), нищо няма да стане, но ако пробваш да прекачиш такъв файл който е генериран с тая програма... тъй де, сигурно има и по лесен начин на флудене, но това е доста компактно.

BTW, споменах го само вяло, но наистина ми е доста интересно как се управлява тая дупка?
« Последна редакция: Aug 29, 2012, 22:14 от shoshon »
Активен

remotex

  • Напреднали
  • *****
  • Публикации: 344
    • Профил
Re: Малък въпрос за lseek()
« Отговор #13 -: Aug 29, 2012, 22:31 »
А защо смени SEEK_SET на SEEK_CUR?

Google: Sparse files

"Sparse files do not have disk space allocated for every block in the whole address space, leading to holes within the file.
Sparse files are basically just like any other file except that blocks that only contain zeros (i.e., nothing) are not actually stored on disk. This means you can have an apparently 16G file – with 16G of “data” – only taking up 1G of space on disk."

http://lwn.net/Articles/357767/
http://administratosphere.wordpress.com/2008/05/23/sparse-files-what-why-and-how/
http://stackoverflow.com/questions/5315428/how-to-create-a-file-with-file-holes
http://linuxgazette.net/174/misc/lg/compressing_sparse_file_s_while_still_maintaining_their_holes.html
« Последна редакция: Aug 29, 2012, 22:38 от remotex »
Активен

shoshon

  • Напреднали
  • *****
  • Публикации: 497
    • Профил
Re: Малък въпрос за lseek()
« Отговор #14 -: Aug 29, 2012, 22:50 »
Цитат
А защо смени SEEK_SET на SEEK_CUR?

Без особена причина. Има ли значение, предвид това, че не следвам някакъв определен алгоритъм?

Ок, значи стигаме до такъв извод:

signed long го превръща в unsigned long когато по подам на lseek. По тази причина каквото пиша отива в края. Само че има един проблем - off_t не е казано че е unsigned. не виждам как (off_t)(-10) ще промени нещо.

Утре ке тестваме и ке разбереме :)
Активен