уторак, 16 априла, 2024
Како да...?

Увод у програмски језик C (10. део)

Аутор: Никола Харди

Увод у рад са датотекама

У претходном делу серијала, започето је разматрање рада са текстом у програмском језику C. Надамо се да смо успели да представимо значај ове теме. У овом наставку серијала биће представљен рад са датотекама. Ток рада класичног програма се углавном дели на три фазе: учитавање података, обрада, испис резултата. У сличним текстовима углавном је акценат на самој обради. Унос података и чување резултата су једнако важне теме.

Приступање датотекама

Први корак у раду са датотекама је „отварање” датотеке. Шта то заправо значи? Без превише приче о оперативним системима, стандардној библиотеци и спрези између корисничких програма и оперативног система, цела идеја се своди на то да програм од оперативног система тражи приступ некој датотеци путем механизма системских позива. Оперативни систем потом програму обезбеђује апстрактну представу те датотеке у виду тока бајтова, без потребе да кориснички програм и програмер познају систем датотека (енг. filesystem) или уређај о којем је реч.

Датотеци је могуће приступити на више нивоа, позивом функције open() која се директно пресликава у системски позив open. Позивом ове функције, оперативни систем корисничком програму обезбеђује дескриптор датотеке (енг. file descriptor) који је у суштини евиденциони број отворене датотеке на нивоу система. Дакле, реч је о обичној целобројној вредности. Рад са датотекама овим механизмом се сматра радом на ниском нивоу, а стандардна библиотека језика C обезбеђује и друге механизме који се такође ослањају на системски позив open, са још неколико додатака.

Други начин, који и препоручујемо, је помоћу структуре FILE и функције fopen(). Разлог за то је што употребом ове уграђене структуре података имамо могућност да користимо и друге стандардне функције за рад са датотекама који ће бити описани.

Датотеке могу бити отворене у више режима као што су режим за читање, режим за писање, режим за додавање садржаја и комбинација претходних. Понекад се у литератури срећу и ознаке за рад са бинарним датотекама, али оне се на већини модерних система једноставно игноришу.

Следи пример отварања датотеке на оба претходно описана начина, а потом и објашњење о режимима у којима је датотека отворена.

#include <stdio.h>
#include <fcntl.h>

int main()
{
   int fd = open("my_file.txt",
                  O_WRONY | O_CREAT, 
                  S_IRUSR | S_IWUSR );
   printf("%d\n", fd);
   write(fd, "LiBRE!\n", 7);
   close(a);

   return 0;
}

Као што можете да видите, ово изгледа мало незграпно. Функција open() има много намена, па је због тога њена употреба и сложенија. Први аргумент је назив (тачније, путања до датотеке). Следећи параметар су подешавања за режим отварања датотеке (само читање и креирање датотеке уколико она већ не постоји). Трећи параметар су подешавања за дозволе датотека (енг. permissions), која у о овом случају подразумева да само тренутни корисник има право писања и читања. Следећи израз је исписивање дескриптора датотека који би требао да буде позитиван број уколико је отварање датотеке било успешно. Након тога, врши се упис текста “LiBRE!\n”, чија је дужина 7 у датотеци са дескриптором датотека fd. По завршетку рада са датотеком, добро је затворити је позивом функције close(), јер тек тада можемо бити сигурни да ће оперативни систем записати сав садржај на диск. У библиотеци <fcntl.h> су дефинисани симболи који су коришћени за подешавање режима и креирање датотеке.

Више детаља је доступно у man страницама којима је могуће приступити командом у терминалу: man 2 open. Друге корисне man странице за рад са датотекама су write, read, као и странице за остале функције које ће бити описане. Функције на вишем нивоу апстракције се налазе у трећем делу приручника, дакле: man 3 fscanf.

Нешто угоднији начин рада са датотекама је помоћу функција fopen(), fwrite(), fread() итд. Они пружају виши ниво апстракције и потребно је мање кода, а и пажње да би све прорадило како треба. Важно је познавати и механизме на које се те функције наслањају, а то су управо поменути системски позиви.

Следи пример кода у којем се приступа датотеци креираној у претходном примеру.

#include <stdio.h>

int main()
{
    char buffer[128];
    
    FILE *f = fopen("my_file.txt", "r");
    fscanf(f, "%s", buffer);
    fclose(f);

    puts(buffer);

    return 0;
}

Овај кôд је знатно читљивији. Нема криптичних параметара ако ништа друго. Функција fopen() има за повратну вредност адресу креиране FILE структуре, а уколико отварање датотеке из неког разлога није било успешно, та адреса је 0. Пожељно је проверити да ли је отварање биле успешно. Параметри ове функције су путања до датотеке и режим приступања. Постојећи режими су:

  • r – само читање
  • r+ – читање и писање од почетка датотеке
  • w – само писање
  • w+ – писање и читање, ако датотека постоји брише се
  • a – писање на крај датотеке
  • a+ – писање на крај и читање

Режими w и a ће креирати нову датотеку уколико она већ не постоји.

Опширнија документација је доступна у man страницама.

Остале функције

Стандардна библиотека садржи много функција које рад са датотекама чине једноставнијим и угоднијим. Међу њима су читање једног карактера, читање стринга, читање линије, читање задатог броја бајтова итд. Све те функције имају и своје парове за писање података. Називи ових функција почињу словом f, а вама препуштамо да погодите њихове називе и потражите детаље у man страницама.

Рад са датотекама не подразумева и рад са текстом. Могуће је чувати и тзв. бинарне податке, као што су структуре, фотографије и сл. Све што је потребно је одредити колико бајтова је потребно учитати или записати, задати са којом датотеком се ради и где сачувати резултат, односно одакле прочитати податке за упис. Величину структуре је могуће сазнати применом оператора sizeof или је једноставно израчунати унапред.

Код рада са датотекама, као и у многим другим ситуацијама, јављају се грешке: нема довољно простора на диску, програм нема дозволу за приступање задатој датотеци, у датотеци не постоје задати подаци итд. Због тога је важно проверавати повратне вредности функција и протумачити њихово значење на основу садржаја man страница.

Неколико речи о сепараторима

Када је реч о раду са текстуалном датотеком, јавља се концепт реда, односно линије текста. Пошто су датотеке само низови дигиталних података, рачунари не познају концепт реда текста. Редови су апстракција позната људима, односно корисницима рачунара, и због тога су вештачки уведени појединим стандардима. Стандард ASCII прописује вредности за више специјалних знакова, као што су, између осталог, нов ред, повратак главе штампача на почетак реда, укључивање звона на знаковном терминалу или штампачу итд. Кроз историју су се јавила два стандарда за означавање краја реда текста, међу програмерима позната као DOS и Јуникс (енг. Unix) стандарди.

У DOS и Виндоуз свету се крај реда означава двама карактерима – \r и \n. Ова конвенција је присутна из историјских разлога и била је присутна у доба линијских штампача којима је задавана експлицитна наредба за прелазак у нов ред и враћање главе на почетак реда. У Јуникс свету је то само један знак, \n. Због тога су могући проблеми при размени датотека са колегама које користе другачије оперативне системе. Симптом је једноставан – садржај целе датотеке је у једном реду или постоје чудни знакови на крају сваког реда. На Линуксу су доступна два једноставна програма која решавају ову збрку, а то су dos2unix и unix2dos. Програми за уређивање текста, такође, могу да буду подешени за рад у једном или другом режиму. Обратите пажњу на овај проблем јер је присутан и дан-данас.

Позиционирање унутар датотеке

Често постоји потреба да се програм позиционира на другу локацију у датотеци. Овај механизам је присутан још из доба када су датотеке биле смештане на магнетним тракама, па према томе и функције имају сличан назив, односно fseek() и rewind(), што би на нашем језику значило „премотати”. Премотавање може да буде унапред или уназад. Референтне тачке могу да буду од почетка датотеке, од тренутне позиције, или од краја датотеке. Постоји и функција ftell() која говори тренутну позицију унутар датотеке. Функција rewind() „премотава” датотеку на њен почетак.

Не измишљајте топлу воду

Модерни информациони системи, базе података, па и рачунарска техника донекле, потекли су једним делом из миљеа корпорација које су се бавиле развојем машина за рад са досијеима. Реч датотека (енг. file) је првенствено представљала досије у картотеци или сличној служби, а посао рачунара је једним делом био да брзо пронађе или похрани податке у одговарајући досије. Од коверти са досијеима прешли смо на концепт датотека, а у разним фазама развоја рачунарских наука дошли смо до концепта база података. Теорија база података и информационих система нису тема ни овог текста, а ни часописа; али важно је напоменути да не треба измишљати топлу воду.

Уколико постоји потреба за чувањем табеларних података, стандардни начин је CSV (енг. Comma Separated Value), односно вредности раздвојене запетама. У стварности, вредности могу да буду раздвојени и другачијим знацима (енгл. delimiterima), размацима, звездицама, усправним цртама итд. Иако је за C честа пракса да се кôд за учитавање оваквих података пише у неколико линија кода, сваки пут – многи други језици и окружења за програмирање нуде аутоматизоване алате и библиотеке за овај посао. Саветујемо да се тај формат испоштује.

Уколико постоји потреба за похрањивањем већег броја података у више датотека, а подаци су притом и увезани, онда је то посао већ за једну базу података. Системи за управљање базама података са једне стране програмеру пружају спрегу за приступ подацима на апстрактном нивоу, а са друге стране сами воде рачуна како ће то сместити на диск. Уз то, пружају и сјајне механизме за брзу претрагу, ефикасно смештање података и прибављање одговора на упите. Постоје релационе и нерелационе базе података, оне за које су потребни сервери и оне које раде у меморији. SQLite је библиотека и концепт базе података за коју није потребно подешавати сервер, креирати корисничке налоге и инстанцирати појединачне базе. Базе се једноставно чувају у једној датотеци која је организована на специфичан начин. Вреди погледати, а свакако ће о овој теми бити речи у неком од наредних бројева.

Полиглоте и шизофреничари

Понекад, ипак, постоји потреба за дефинисањем новог типа датотеке. Тако су прописани формати датотека као што су ZIP, PDF, ELF, JPEG итд. Добро је питање и огроман проблем како правилно описати структуру једне датотеке, како препознати тип датотеке и проверити да ли је структура валидна и, још горе, како то преточити у функционалан програм. Познати су безбедносни проблеми који се базирају управо на овим проблемима. Злонамерни програми могу бити сакривени унутар друге датотеке, рецимо унутар PDF документа, или више програма може бити спојено унутар једне извршне датотеке. Отуда и назив овог одељка – полиглотaма се сматрају датотеке које могу бити протумачене у више формата, рецимо PDF и фотографија. Шизофреничари су датотеке које свој садржај приказују другачије у зависности од различитих програма за прегледање – например, Evince приказује PDF са једним садржајем, а Окулар PDF са другим садржајем.

Овом темом се бави Анге Албертини. Он је на овогодишњем CCC-у (енг. Chaos Communication Congress) одржао предавање у којем је описао ове проблеме. Предавање је доступно на сајту http://media.ccc.de под називом „Funky file formats”. Неке од ових техника су демонстриране у часопису PoC||GTFO, који је једноставно пронаћи на интернету. У њима су описани и детаљи о начину на који је могуће направити овакве датотеке. На сајту http://www.corkami.com постоје, између осталог, и постери који описују структуру неких познатих типова датотека. Предлажемо да погледате о чему је реч. Оформила се и посебна група истраживача који се донекле баве овом темом, а реч је о теми под називом LANGSEC (http://langsec.org/).

У наредном тексту

Наредним текстом ће овај серијал полако бити приведен крају. Обрађене су најважније теме овог програмског језика. Временом су текстови непланирано порасли, али не замерите. У наредном тексту (највероватније ће то бити више од једног текста), биће описане идеје које превазилазе сам језик, смернице шта занимљиво може да се направи, корисне библиотеке и други важни концепти. Спремни сте да направите и свој први програм који ћете моћи да користите свакодневно. Јавите нам се уколико желите да обрадимо неку тему детаљније или да додамо још који текст у серијал.

Наредни део овог серијала можете прочитати овде.

Претходни део овог серијала можете прочитати овде.