FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

Opravujeme chyby v softwaru: inotify-tools

vydáno: 4. 10. 2015 12:25, aktualizováno: 26. 8. 2018 14:13

V pátek jsem narazil na chybu v programu inotifywait a dneska jsem ji ze zvědavosti trochu prozkoumal. Program za určitých okolností padal (SIGSEGV, core dump). Jedná se o celkem banální chybu, kterých je všude plno, a rád bych na ní ukázal, jak v takovém případě můžeme postupovat.

string = csv

Co je inotify a inotify-tools

Inotify je subsystém Jádra (Linuxu), který umožňuje aplikacím reagovat na události v souborovém systému – aplikace od jádra dostává události např. o tom, že byl nějaký soubor vytvořen, smazán, otevřen nebo upraven, a může na ně reagovat. Díky tomu nás třeba textový editor upozorní, že došlo ke změně souboru, který právě editujeme (např. jsme otevřeli tentýž soubor ve dvou editorech a upravujeme ho paralelně). Nebo můžeme po uložení .sql souboru spustit databázový dotaz nebo třeba po uložení .xhtml aktualizovat stránku v prohlížeči.

Inotify-tools je balíček obsahující programy inotifywaitinotifywatch, které používají subsystém inotify a umožňují nám reagovat na události třeba v bashových skritech nebo prostě jen sledovat, co se na souborovém systému děje (k tomu nemusíme nic programovat, prostě jen spustíme jeden z těchto příkazů).

Příklad: budeme sledovat adresář test:

$ inotifywait -m -r test/
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
test/ MODIFY soubor.txt
test/ OPEN soubor.txt
test/ MODIFY soubor.txt
test/ CLOSE_WRITE,CLOSE soubor.txt

Události se nám vypsaly poté, co jsme v druhém terminálu spustili:

echo ahoj > test/soubor.txt

Díky inotifywait tedy krásně vidíme, co se na souborovém systému stalo – došlo k vytvoření souboru, jeho otevření, úpravě a zavření se zápisem.

Popis chyby

Příkaz inotifywait má volbu --csv, která zajistí výstup ve formátu CSV (hodnoty oddělené čárkou). Chyba se projevuje jen při jejím použití – jde tedy o chybu někde ve formátovači CSV výstupu. A přišel jsem na ni díky své škodolibosti: jestliže CSV obsahuje hodnoty oddělené čárkou, co se asi stane, když název souboru nebo adresáře bude obsahovat čárku?

Pokud čárku obsahuje název souboru:

echo ahoj > test/sou,bor.txt

nestane se nic, resp. název se správně obalí uvozovkami:

$ inotifywait --csv -m -r test/
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
test/,CREATE,"sou,bor.txt"
test/,OPEN,"sou,bor.txt"
test/,MODIFY,"sou,bor.txt"
test/,"CLOSE_WRITE,CLOSE","sou,bor.txt"

Pokud ale čárku obsahuje název adresáře,

$ mkdir test/adre,sář
$ echo ahoj > test/adre,sář/soubor.txt

program spadne:

$ inotifywait --csv -m -r test/
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
test/,"CREATE,ISDIR","adre,sář"
test/,"OPEN,ISDIR","adre,sář"
test/,"ACCESS,ISDIR","adre,sář"
test/,"CLOSE_NOWRITE,CLOSE,ISDIR","adre,sář"
Neoprávněný přístup do paměti (SIGSEGV) (core dumped [obraz paměti uložen])

a spadne až po vykonání příkazu echo, ne po mkdir – tzn. spadne při vytváření souboru, který se nachází v adresáři, jehož název obsahuje čárku.

Protože inotify-tools je svobodný software, nemusíme na neposlušný počítač jen smutně koukat nebo mu nadávat a můžeme s tím něco dělat – pojďme tedy na to – podíváme se, jak program uvnitř funguje, a pokusíme se ho opravit.

Příprava prostředí

Nejprve si vytvoříme oddělené prostředí, ve kterém budeme tento problém řešit – jde jednak o bezpečnost (budeme stahovat cosi z Internetu a následně to kompilovat a spouštět) a jednak o izolaci aplikací – nechceme, aby nám jedna aplikace ovlivňovala chod jiných aplikací.

GNU/Linux nám k tomu poskytuje různé možnosti – od plné virtualizace (KVM, Xen, VirtualBox), přes kontejnerovou (LXC, OpenVZ), různé bezpečnostní moduly (AppArmor, SELinux) po unixové základy, jako jsou uživatelské účty a souborová oprávnění.

Vytvoření uživatele

V tomto případě si vystačíme s jednoduchým řešením: založíme si druhý uživatelský účet pojmenovaný třeba hacker, pod kterým budeme pracovat (tento uživatel nemůže číst ani zapisovat soubory našeho hlavního uživatele).

# adduser hacker

Instalace potřebných nástrojů

Dále budeme potřebovat balíčky jako build-essentials, make, gcc, gdb, autoconf, automake nebo libtool. Obecně platí, že pokud se na něčem zaseknete, pravděpodobně je to tím, že vám některý z těchto nástrojů chybí – stačí si přečíst chybové hlášky a doinstalovat ho.

Zdrojové kódy budeme v něčem upravovat, potřebujeme tedy editor (např. Emacs, VIM, mcedit nebo jEdit) nebo nějaké IDE. Osobně dávám přednost IDE kvůli lepšímu ladění a pohodlnější práci. Doporučuji Netbeans, což je sice IDE primárně pro Javu, ale má velice dobrou podporu i pro C/C++ a integraci s GDB (GNU Debugger), takže ladění C/C++ programů je v něm skoro tak pohodlné, jako ladění programů v Javě.

Netbeans už instalujeme pod uživatelem hacker.

Kompilace a hledání chyby

Získání zdrojových kódů

Z balíčkovacího systému (v mém případě: aptitude show inotify-tools) zjistíme domovskou stránku programu a následně cestu k úložišti zdrojáků, odkud si je stáhneme:

$ git clone https://gitlab.com/src-backup.globalcode.info/inotify-tools.git

Kompilace

Není od věci si přečíst soubory READMEINSTALL. Nebo se můžeme řídit intuicí a zkušenostmi. Vidíme soubor autogen.sh, tak ho spustíme a on nám vygeneruje soubor configure, spustíme i ten a máme makefile a spustíme make:

$ ./autogen.sh
$ ./configure
$ make

Příkazy toho vypisují poměrně dost, je dobré číst aspoň konce těch výpisů a kontrolovat, jestli se na nich nevyskytuje chyba (typicky nějaká chybějící závislost – viz výše).

Spuštění

Po úspěšné kompilaci nám vznikl soubor src/inotifywait. Ten spustíme – teď už spouštíme námi zkompilovaný program, ne ten z distribuce.

Mimochodem, než začneme věci opravovat nebo se v něčem vrtat, vždy je potřeba rozběhat původní verzi – až když se nám to povede, tak teprve upravujeme zdrojáky – jinak jen těžko rozlišíme, jestli jsme nefunkčnost způsobili svým zásahem nebo byl program rozbitý už dříve.

Reprodukce chyby a hledání příčiny

Když máme spustitelný program, zkusíme nasimulovat chybu (viz výše). S překvapením ale zjišťujeme, že k této chybě už nedochází, program nepadá.

Proč s překvapením? Přeskočil jsem hodně důležitou fázi, kterou je rešerše nahlášených chyb – je poměrně vzácné, abychom byli první, kdo na chybu narazil, proto je dobré nejdřív prohledat systémy na správu chyb/požadavků (většina projektů používá Bugzillu, Trac, GitLab, Launchpad nebo něco podobného) a zjistit, zda již chyba nebyla hlášena nebo dokonce opravena v novější verzi.

Na stránkách projektu jsem se totiž dočetl:

inotify-tools 3.14 is the latest version, released on the 7th of March 2010.

což je už dost dávno a ve své distribuci verzi 3.14 mám, a proto jsem rešerši neprovedl.

Možná vysvětlení jsou a) v Debianu/Ubuntu kompilují program jinak a jejich 3.14 se chová jinak než moje 3.14, b) v Debianu/Ubuntu zanesli do programu chybu ve formě vlastního patche, c) nějaký vývoj od roku 2010 přeci jen probíhá.

Stáhl jsem si tedy zdrojový balíček z Ubuntu:

$ apt-get source inotify-tools

a pomocí:

$ kompare inotify-tools-3.14/src/ inotify-tools/src/

jsem našel, čím se mj. liší:

příčina chyby v inotify-tools – dvojité escapování

V Netbeans jsem pomocí funkce Team / Show Annotations zjistil historii tohoto řádku:

oprava chyby v inotify-tools - Netbeans

(stejně tak můžeme použít příkaz git annotate src/inotifywait.c)

C je tedy správně – vývoj probíhá a chyba již byla nahlášena (#36) i opravena (e049c88). Jen tato změna ještě nebyla z vývojové verze převzata do Debianu/Ubuntu (tam se používá poslední vydaná 3.14).

Problém spočívá v tom, že se escapuje text, který už byl jednou escapován (viz řádek 126). Že je to skutečně příčina chyby, jsem ověřil tak, že jsem řádek:

printf("%s,", csv_escape(filename));

zanesl i do svého programu – a ten začal padat.

Teď by tedy stačilo kontaktovat správce balíčků v distribuci, aby začlenili příslušnou změnu od autorů programu…

Stále je to rozbité

Ovšem když se nad tím trochu zamyslíme: proč by sakra program měl padat, když se něco escapuje dvakrát? Maximálně by měl dávat chybný výsledek, ale rozhodně by neměl padat. Chyba bude tedy někde hlouběji a změna e049c88 řeší pouze dílčí problém resp. jinou chybu, díky které se na tu skutečnou přišlo.

Podívejme se tedy na funkci csv_escape():

char * csv_escape( char * string ) {
	static char csv[MAX_STRLEN+1];
	static unsigned int i, ind;

	if (string == NULL) {
		return NULL;
	}

	if ( strlen(string) > MAX_STRLEN ) {
		return NULL;
	}

	if ( strlen(string) == 0 ) {
		return NULL;
	}

	// May not need escaping
	if ( !strchr(string, '"') && !strchr(string, ',') && !strchr(string, '\n')
		    && string[0] != ' ' && string[strlen(string)-1] != ' ' ) {
		strcpy( csv, string );
		return csv;
	}

	// OK, so now we _do_ need escaping.
	csv[0] = '"';
	ind = 1;
	for ( i = 0; i < strlen(string); ++i ) {
		if ( string[i] == '"' ) {
			csv[ind++] = '"';
		}
		csv[ind++] = string[i];
	}
	csv[ind++] = '"';
	csv[ind] = '\0';

	return csv;
}

Už na první pohled je podezřelých několik věcí. Proměnná csv se alokuje s velikostí MAX_STRLEN+1 a vstup se kontroluje, aby nebyl větší než MAX_STRLEN, ale při escapování se text prodlouží, takže je jasné, že se do MAX_STRLEN+1 nemusí vejít.

A ty statické lokální proměnné jsou taky divné – asi nějaká céčkařská optimalizace, které jako javista nemůžu rozumět…

debuggeru (GDB + Netbeans) už po pár průchodech cyklem vidíme, že se děje něco nekalého – rostoucí počet uvozovek:

ladění inotify-tools v Netbeans/GDB

Přidáme si do kódu ladící výpis:

for ( i = 0; i < strlen(string); ++i ) {
	printf("%d / %d / %s / %s\n", i, ind, string, csv); // ladíme
	if ( string[i] == '"' ) {
		csv[ind++] = '"';
	}
	csv[ind++] = string[i];
}

a výstup nasměrujeme do souboru:

…
24 / 25 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotif
25 / 26 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify
26 / 27 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/
27 / 28 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a
28 / 29 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a,
29 / 30 / /home/hacker/temp/inotify/a,b/ / "/home/hacker/temp/inotify/a,b
0 / 1 / "/home/hacker/temp/inotify/a,b/" / "/home/hacker/temp/inotify/a,b/"
1 / 3 / """ome/hacker/temp/inotify/a,b/" / """ome/hacker/temp/inotify/a,b/"
2 / 5 / """""e/hacker/temp/inotify/a,b/" / """""e/hacker/temp/inotify/a,b/"
3 / 7 / """""""hacker/temp/inotify/a,b/" / """""""hacker/temp/inotify/a,b/"
4 / 9 / """""""""cker/temp/inotify/a,b/" / """""""""cker/temp/inotify/a,b/"
5 / 11 / """""""""""er/temp/inotify/a,b/" / """""""""""er/temp/inotify/a,b/"
6 / 13 / """""""""""""/temp/inotify/a,b/" / """""""""""""/temp/inotify/a,b/"
7 / 15 / """""""""""""""emp/inotify/a,b/" / """""""""""""""emp/inotify/a,b/"
8 / 17 / """""""""""""""""p/inotify/a,b/" / """""""""""""""""p/inotify/a,b/"
9 / 19 / """""""""""""""""""inotify/a,b/" / """""""""""""""""""inotify/a,b/"
10 / 21 / """""""""""""""""""""otify/a,b/" / """""""""""""""""""""otify/a,b/"
11 / 23 / """""""""""""""""""""""ify/a,b/" / """""""""""""""""""""""ify/a,b/"
12 / 25 / """""""""""""""""""""""""y/a,b/" / """""""""""""""""""""""""y/a,b/"
13 / 27 / """""""""""""""""""""""""""a,b/" / """""""""""""""""""""""""""a,b/"
14 / 29 / """""""""""""""""""""""""""""b/" / """""""""""""""""""""""""""""b/"
15 / 31 / """""""""""""""""""""""""""""""" / """"""""""""""""""""""""""""""""
16 / 33 / """"""""""""""""""""""""""""""""" / """""""""""""""""""""""""""""""""
17 / 35 / """"""""""""""""""""""""""""""""""" / """""""""""""""""""""""""""""""""""
…
3789 / 7608 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…
3790 / 7610 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…
3791 / 7612 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…
3792 / 7614 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…
3793 / 7616 / """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""…

Zde vidíme že první volání funkce proběhlo v pořádku: pro vstup /home/hacker/temp/inotify/a,b/ funkce vrátila "/home/hacker/temp/inotify/a,b/".

Zatímco druhé (nežádoucí, ale na tom teď nezáleží) volání funkce donekonečna přidávalo uvozovky, resp. přidávalo by, kdyby program nebyl zastaven operačním systémem kvůli tomu, že se proces pokusil zapsat do paměti někam, kam neměl (SIGSEGV).

Na tomto příkladě hezky vidíme, že u programů psaných v jazyce C nezáleží na velikosti pole (MAX_STRLEN je 4096), program tuto hranici vesele překročil, přepisoval si svojí paměť dál mimo dané pole a zarazil ho až operační systém v bodě 7616.

Celé je to ale nějaké divné, protože for cyklus by měl iterovat od nuly do strlen(string) a pak se zastavit, ne pokračovat donekonečna. Leda že by se hodnota strlen(string) v čase měnila. Upravíme si tedy ladící výpis, abychom věděli víc:

printf("i = %u / ind = %u / strlen(string) = %zu / vstup = %s / výstup = %s\n", i, ind, strlen(string), string, csv);

A vidíme, že délka string od bodu i = 15 a hodnoty 32 skutečně roste:

…
i = 13 / ind = 27 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""a,b/" / výstup = """""""""""""""""""""""""""a,b/"
i = 14 / ind = 29 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""""b/" / výstup = """""""""""""""""""""""""""""b/"
i = 15 / ind = 31 / strlen(string) = 32 / vstup = """""""""""""""""""""""""""""""" / výstup = """"""""""""""""""""""""""""""""
i = 16 / ind = 33 / strlen(string) = 33 / vstup = """"""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""
i = 17 / ind = 35 / strlen(string) = 35 / vstup = """"""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""
i = 18 / ind = 37 / strlen(string) = 37 / vstup = """"""""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""""
i = 19 / ind = 39 / strlen(string) = 39 / vstup = """"""""""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""""""
i = 20 / ind = 41 / strlen(string) = 41 / vstup = """"""""""""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""""""""
i = 21 / ind = 43 / strlen(string) = 43 / vstup = """"""""""""""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""""""""""
i = 22 / ind = 45 / strlen(string) = 45 / vstup = """"""""""""""""""""""""""""""""""""""""""""" / výstup = """""""""""""""""""""""""""""""""""""""""""""
…
i = 2045 / ind = 4091 / strlen(string) = 4091 / vstup = """""""""""""""""""""…
i = 2046 / ind = 4093 / strlen(string) = 4093 / vstup = """""""""""""""""""""…
i = 2047 / ind = 4095 / strlen(string) = 4095 / vstup = """""""""""""""""""""…
i = 2048 / ind = 4097 / strlen(string) = 4097 / vstup = """""""""""""""""""""…
i = 2049 / ind = 4099 / strlen(string) = 4099 / vstup = """""""""""""""""""""…
i = 2050 / ind = 4130 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2051 / ind = 4132 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2052 / ind = 4134 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2053 / ind = 4136 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2054 / ind = 4138 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 2544 / ind = 5118 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2545 / ind = 5120 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 2546 / ind = 5122 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 2672 / ind = 5374 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2673 / ind = 5376 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 2674 / ind = 5378 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 2800 / ind = 5630 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2801 / ind = 5632 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 2802 / ind = 5634 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 2928 / ind = 5886 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 2929 / ind = 5888 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 2930 / ind = 5890 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3056 / ind = 6142 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3057 / ind = 6144 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3058 / ind = 6146 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3184 / ind = 6398 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3185 / ind = 6400 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3186 / ind = 6402 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3312 / ind = 6654 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3313 / ind = 6656 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3314 / ind = 6658 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3440 / ind = 6910 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3441 / ind = 6912 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3442 / ind = 6914 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3568 / ind = 7166 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3569 / ind = 7168 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3570 / ind = 7170 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3696 / ind = 7422 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3697 / ind = 7424 / strlen(string) = 4100 / vstup = """""""""""""""""""""…
i = 3698 / ind = 7426 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
…
i = 3792 / ind = 7614 / strlen(string) = 4102 / vstup = """""""""""""""""""""…
i = 3793 / ind = 7616 / strlen(string) = 4102 / vstup = """""""""""""""""""""…

Většinou roste po dvou, až se ustálí na hodnotě 4102, ovšem s občasnými anomáliemi 4100.

Když zkrátíme MAX_STRLEN na 100, roste hodnota na 105, kde se zastaví (a program nestihne spadnout). A za tou hromadou uvozovek je vždy ještě další znak a na každém řádku jiný (že by to byl občas nulový bajt a tím pádem délka 4100 místo 4102? :-).

Jak se může stát, že délka proměnné string roste, když do string v průběhu cyklu nic nepřiřazujeme?

Vzpomínáte na ty statické lokální proměnné?

Statická lokální proměnná je něco jako globální proměnná, taky je v programu jenom jednou, akorát je vidět jen z té jedné funkce – a při každém volání té funkce je vidět ta samá.

Věci tedy nejsou, jaké se zdají být, vstup je výstupem, string je csv. Teď už chápu, co se mi GDB snažil říct tím <csv> u proměnné string při druhém průchodu funkcí:

string = csv

Funkce csv_escape() tedy funguje správně jen za předpokladu, že nechcete volat něco jako csv_escape(csv_escape(…)), protože jakmile pozře svůj vlastní výstup, tak se zacyklí.

Možná by stačilo nahradit return csv; vracením kopie, ale pak je zase otázka, zda by nedocházelo k únikům paměti… pořádnou opravu raději přenechám někomu, kdo umí C líp. A můžeme se vrátit někam doprostřed našeho snažení: je potřeba dostat opravu e049c88 do distribucí.

Za sebe tuhle část zakončím citátem autora knih The Art of Computer Programming a typografického systému TeX, Donnalda Knutha:

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

Závěr

I když oprava chyby nedopadla úplně podle mých představ a dokonalého řešení jsem dnes nedosáhl, doufám, že i tak vás tento článek povzbudí k hledání a opravování chyb nebo alespoň jejich hlášení. Svobodný software nám to umožňuje – tak ho pojďme dělat (ještě) lepším!

Abyste napsali dobrý (reprodukovatelný) popis chyby nemusíte umět programovat vůbec. A i když neovládáte daný programovací jazyk, stále můžete najít, kde přesně se chyba nachází a v čem spočívá – ostatně to je ten největší kus práce – oprava pak bývá obvykle na pár řádek.

Přílohy:

Odkazy a zdroje:

Témata: [svobodný software] [GNU/Linux] [C]

Komentáře čtenářů


Pavel Kysilka, 5. 10. 2015 09:11, super clanek [odpovědět]

zdravim,

super clanek. Presne jste trefil tema, o kterem se moc nepise, ale presto jej denne resime.

gf


uetoyo, 5. 10. 2015 11:01 [odpovědět]

Pěkný článek, dík za něj -- proklik z root.cz


mikro, 5. 10. 2015 11:51 [odpovědět]

fuuj, tak to je ina prasaren. ciste riesenie samozrejme (v Ccku) je to, ze na vstup dodame vystupny buffer aj s informaciou o jeho velkosti.


Franta, 5. 10. 2015 22:06, buffer jako parametr [odpovědět]

To jsem vlastně navrhoval tady:

I had problems with CSV output. I analyzed the source code and did some debugging and then I realized, that the bug #36 was already fixed upstream and it "just" remains in distributions.

But anyway the function csv_escape() IMHO should be improved or at least well documented because it has some subtle limitations:

  • the input length is checked against MAX_STRLEN but during escaping the length grows so it will not fit into the char csv[MAX_STRLEN+1]. Of course, current filesystems have also limitations so it might not be an issue. But the limits could be checked inside the loop. And on the other hand, the strlen(string) could be done only once (outside the loop).
  • using static local variables and returning pointers to them from the function might lead (and really leads, as seen in #36) to very unexpected behavior. What about passing the buffer from outside the function as its parameter?

BTW: are you going to release 3.15? (according to the wiki, the last released version is still 3.14 from 2010)

přijde mi to trochu těžkopádné, ale asi to jinak nejde.

BTW: nemáš tip na nějaký dobrý kód v C pro inspiraci? Případně C++ (to je zase trochu jiná kapitola). Ale něco menšího, Jádro asi jen tak nepřečtu :-)


mikro, 6. 10. 2015 09:38 [odpovědět]

No tak prave kernel by som za priklad na dobry C kod nevydaval. :) Tym, ze tam prispieva plno ludi, tak to aj podla toho vyzera. Zial takto z hlavy si na nejaky projekt nespomeniem, zalezi, aj co od toho cakas, ci sa chces nieco nove naucit (tam mozno pomoze skor dobra kniha) alebo len tak zo zvedavosti. :)


Franta, 7. 10. 2015 22:19 [odpovědět]

Spíš se něco naučit, odkoukat reálné příklady z praxe. Učebnicové příklady obvykle vysvětlují jednu věc, ale chybí tomu kontext celého programu kolem toho. Něco v podobném rozsahu jako ty inotify-tools (něco přes 3 000 řádků kódu).


Kruci, 5. 10. 2015 19:33 [odpovědět]

"static char csv[MAX_STRLEN+1];" je globalni promenna viditelna jen v te funkci. Smysl muze byt optimalizace i usnadneni pouziti, nemusite alokovat/dealokovat novy retezec. Tento kod neni bezpecny z pohledu vlaken (neni thread-safe), coz tedy asi neni problem. Take je potreba davat pozor na to, jak se pracuje s vyslednou hodnotou funkce (vraci ukazatel na globalni pole).
"static unsigned int i, ind;" se zda zbytecny, napada me jen usetreni mista na zasobniku (call stack).

Pole v C je vlastne jen "blok pameti", o ktery se musime starat sami.
"nahradit return csv; vracením kopie" by jak pisete zpusobilo uniky pameti (zbytek kodu nepocita s alokovanym retezcem).

Oprava chyb nalezenych v clanku v duchu toho kodu by mohla byt:
Zmena:

// Max array len = all chars need escape + quotes + null terminator
static char csv[2*MAX_STRLEN+2+1];

Pridani:

// May be already escaped
if (string == csv) {
    return csv; // already escaped, in string is result of this function
}

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře