Jít na obsah Jít na navigaci Jít na vyhledávání

Šetříme interní paměť v T-Mobile G1 - Android

Po updatu na CyanogenMod 4.2.7 se mi v trayi telefonu opět objevila nepříjemná ikonka signalizující nedostatek místa v interní paměti telefonu. Na tom by nebylo zas až tak nic špatného, kdyby mi z nenadání přestala fungovat synchronizace a download aplikací v Marketu. Obětoval jsem celé odpoledne na to, abych zjistil, že onu nefunkční synchronizaci má za následek právě nedostatek místa, na který jsem byl non-stop upozorňován. Takže co s tím?

Apps2sd

Řešení integrované přímo do CyanogenModu spočívající v jednoduchém shell skriptu, který pří bootu telefonu na základě předpokládané existence druhého diskového oddílu na SD kartě tento oddíl použije a přesune na něj veškeré nainstalované aplikace a data s nimi související. Nevýhodou je předchozí úprava karty pomocí nástrojů na dělení disku a naformátování na linuxový filesystém ext2/3/4. Za odměnu ale dostanete přenositelné aplikace i s nastavením. Skript pracuje zcela automaticky, tudíž stačí jen mít připravenou partition.

Mé vlastní řešení

Přišel jsem na něj doslova metodou pokus-omyl. Při šťourání se v datové partition jsem si všiml adresáře /data/dalvik-cache, který zabíral úctyhodných 40 MB a tím pádem byl ihned nominován za největšího žrouta místa. Tento adresář slouží běhovému prostředí Dalvik VM jako cache optimalizovaného bytekódu, jinými slovy jako úložiště překompilovaných a upravených binárek používaných aplikací. Do detailu Dalvik buduje strom závislostí jednotlivých tříd (funkčních celků aplikace) tak, aby nainstalovaná aplikace běžela co možná nejlépe. Tento strom je regenerován při každé aktualizaci ROM, proto první boot po updatu trvá i několik minut.

Dalším zajímavým místem je adresář /cache, který je stále téměř prázdný. V dosavadním Androidu je použit výhradně jako dočasné úložiště pro stahované aplikace v Marketu, jenže v poměru velikosti celé parititon (~60 MB) a jednoho stahovaného balíčku (řádově jednotky MB) je až škoda toto místo nevyužít. Proto jsem si napsal krátký skript, který dalvik-cache přesune do tohoto volného prostoru.

#!/system/bin/sh

# move dalvik cache from internal data memory to /cache
if [ ! -d /cache/dalvik-cache ];
then
    mkdir /cache/dalvik-cache
fi

busybox chown 1000:1000 /cache/dalvik-cache
busybox chmod 771 /cache/dalvik-cache

if [ -d /data/dalvik-cache ] && [ ! -h /data/dalvik-cache ];
then
    busybox cp -a /data/dalvik-cache/* /cache/dalvik-cache
    busybox rm -f /data/dalvik-cache/*

# bind mount dalvik-cache
busybox mount -o bind /cache/dalvik-cache /data/dalvik-cache

Tento skript stačí umístit např. jako /etc/init.d/06custom. Toho můžeme efektivně docílit vytvořením souboru na SD kartě a následným zkopírováním do systému těmito příkazy:

mount -o remount,rw /system
cp /sdcard/06custom /etc/init.d
chmod 755 /etc/init.d/06custom

… a pokračovat rebootem telefonu.

Pokud jste vše udělali správně, máte nyní v interní paměti (aka /data) okolo 40 MB volného místa. Povedlo se!

Pozn.: Uvedený postup je třeba opakovat po každém updatu ROM, kdy dojde k přepsání systémové partition a tedy i vlastního skriptu. Doporučuji proto skript na SD kartě ponechat a před bootem do updatlé ROM spustit konzoli a zopakovat příkazy uvedené výše.

Štítky:

Polyinstancování adresáře /tmp

Trocha teorie

Dlouholetým problémem UNIXově založených operačních systémů je adresář /tmp sloužící jako dočasné úložiště pro všechny. Tento adresář má práva nastavená na rwxrwxrwx, tedy zápis pro kohokoli. Nevýhodou však byl i absolutní přístup k datům jiných uživatelů, a proto byl zaveden tzv. sticky bit, který omezuje zápis pouze na soubory a adresáře vlastněné patřičným uživatelem. Každý tedy může zapisovat nové soubory, ale jen do těch existujících, které vlastní.

Novou možnost fyzické separace pak nabízí tzv. polyinstantia­tion. Každému uživateli namapuje daný adresář na jiný, pro něj v původní cestě nepřístupný. V případě adresáře /tmp by se např. jednalo o mapu z /tmp-inst nebo klidně i /tmp/users. Mechanismus, který polyinstancování zajišťuje, automaticky vytvoří adresář dle uživatelského jména a jím nahradí cílový adresář dle mapy.

Ukázka z praxe

Nastavení se provádí v konfiguračním souboru /etc/security/namespace.conf prostřednictvím PAM modulu pam_namespace.so:

# /etc/security/namespace.conf
#
# See /usr/share/doc/pam-*/txts/README.pam_namespace for more information.
#
#/tmp     /tmp-inst/            both      root,adm
#/var/tmp /var/tmp/tmp-inst/    both      root,adm
#$HOME    $HOME/$USER.inst/inst- context
/tmp      /tmp/users/           user      root

Zakomentované příklady zahrnují polyinstancing /tmp, /var/tmp (který osobně doporučuji nastavit jako symbolický odkaz do /tmp) a domovského adresáře uživatele. Důležitý parametr je typ, který zpravidla může být:

  • user (na základě uživatelského jména)
  • context (na základě bezpečnostního kontextu, pouze SElinux)
  • both (obojí)

Poslední čtvrtý parametr udává výjimky. V mém případě bude na základě uživatelského jména namapován adresář /tmp z /tmp/users s výjimkou uživatele root.

Jak na to

Nejprve vytvoříme adresář, ze kterého budeme mapovat, v mém případě /tmp/users. Tomuto adresáři nastavíme z bezpečnostních důvodů nulová práva a zadáme mapu v konfiguračním souboru /etc/security/namespace.conf.

Nyní je třeba pečlivě zvážit nastavení modulů PAM. Po zkušenostech z praxe nedoporučuji spouštět PAM modul ve všech případech autentizace (soubor /etc/pam.d/common-session, způsobí to více problémů než užitku. Polyinstancování jsem proto zúžil na dva jediné typy autentizace – login a sshd.

Ukázka začlenění PAM modulu do souboru s autentizací:

# Enable polyinstantiation
session    required   pam_namespace.so

Je třeba dát si pozor na to, kam modul umístíme. Existují místa, kdy je moc brzy a kdy zase moc pozdě – lepší je si to párkrát zkusit a pozorně sledovat logy (např. auth.log). Pokud se mechanismus aplikuje, měli bychom po novém přihlášení mít adresář /tmp v ideálním případě prázdný. Přihlásíme-li se na roota, měli bychom vidět /tmp/users a v něm adresář s naším a případně dalšími uživatelskými jmény. Máme hotovo.

Osobně mám přímé přihlášení na root uživatele zakázané a přistupuji přes sudo. Bohužel v tomto případě se mi nepodařilo sudo nijak donutit vyvolat čistý login (nejspíše proto, že sudo je spouštěno z shellu, na který již bylo polyinstacování aplikované), takže ani po přihlášení na roota mi /tmp neodhalil žádná svá tajemství. Pokud nedejbože nutně potřebuji vidět obsah neořezaného /tmp, obejdu bezpečnostní mechanismus mountnutím root partition např. do /mnt a podívám se tam :)

Štítky:

Awstats rychle a jednoduše

Awstats je skript v Perlu, který zpracovává logy (nejen) webserveru a ve výsledku vyplivne pěknou statistiku přístupů. Dnes si ukážeme, jak Awstats lehce a efektivně nastavit.

Awstats

Standardní konfigurace Awstats se nachází v adresáři /etc/awstats, kde je i ukázkový soubor awstats.conf. Jelikož konfigurace podporuje funkci Include, využijeme tento soubor jako základ pro naše vlastní konfigurace.

Vytvoříme nový soubor awstats.domain.tld.conf:

# hlavní config, nejprve jej vhodně nastavíme (formát logů, pluginy atd.)
Include "/etc/awstats/awstats.conf"
# log soubor Apache (v mém případě)
LogFile="/home/www/logs/domain.tld.log"
# doména, povinný parametr, který se spíše uplatní při použití spojeného logu (o tom později)
SiteDomain="domain.tld"
# aliasy pro SiteDomain, doporučuji tento formát
HostAliases="REGEX[domain\.tld$]"

Apache 2

Nastavíme Apache, aby logoval. Direktiva CustomLog se může nacházet uvnitř i vně direktivy <VirtualHost>, podle toho, co všechno chceme logovat.

Jeden log pro každou doménu

Direktivu přidáme do odpovídajícího VirtualHosta:

<VirtualHost *:80>
    ServerName domain.tld
    ...
    CustomLog /home/www/logs/domain.tld.log combined
</VirtualHost>

Jeden log pro celý server

Přidáme mimo VirtualHost:

# vlastní formát logu, prefixem je jméno VirtualHosta
LogFormat "%{Host}i %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" cplus
CustomLog /home/www/logs/server.log cplus

V případě této konfigurace musíme Awstats říct o novém formátu logu, což provedeme přímo v konfiguraci awstats.conf úpravou řádky LogFormat:

LogFormat="%virtualname %host %other %logname %time1 %methodurl %code %bytesd %refererquot %uaquot"

Další nastavení

Alias /awstats-icon/ /usr/share/awstats/icon/
<Directory /usr/share/awstats/icon>
    Options None
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

Namapuje adresář s obrázky Awstats do URL adresy /awstats-icon/.

Zpracování

K periodické aktualizaci dat nastavíme cron (pokud již tak distributor neudělal):

*/15 *  * * *   root    /home/bin/awstats_updateall.pl now --excludeconf=awstats.conf > /dev/null 2>&1

Skript awstats_updateall.pl a dalších pár šikovných skriptů nalezneme v adresáři /usr/share/doc/awstats/examples. Tento skript zpracovává všechny konfigurační soubory, takže aktualizuje všechny domény.

Máme-li správně nastavené CGI, což ve výchozí konfiguraci obvykle bývá, nalezneme webový frontend awstats na adrese http:domain.tld/cgi-bin/awstats.pl?con­fig=domain.tld//. Kromě řešení na bázi Perl frontendu a awstats_updateall.pl skriptu je však možné využít awstats_buildstaticpages.pl, který přímo vygeneruje HTML soubory do adresáře zadaného v parametru. Toto řešení převážně využívám na webhostingách, má však tu nevýhodu, že není možné zobrazit statistiky za jiná období a zákazník vždy vidí pouze aktuální měsíc.

Vylepšení

Napsal jsem rewrite, který podstatně zkrášluje adresu a parametr předávaný skriptu /cgi-bin/awstats.pl na webu:

RewriteEngine On
RewriteRule ^/?([^/]+)/$ /cgi-bin/awstats.pl?config=$1 [PT,L,QSA]

Výše zmíněný kód je vhodné aplikovat na určitou subdoménu, např. stats. Statistiky pak nalezneme na adrese http://stats.anotherdomain.tld/domain.tld/.

Statistiky subdomén

Awstats se samozřejmě dá nastavit tak, aby u určité domény logoval i každou subdoménu. Tento přístup kombinuje a upravuje formáty logů popsané výše. Každá subdoména musí mít svůj VirtualHost a definovaný log, v optimálním případě budou logy pro každou doménu zvlášť a v nich spojené logy subdomén s upraveným formátem (viz. jeden log pro celý server).

Štítky:

Vlastní chybové hlášky v Apachi a PHP

Standardní chybové hlášky v Apachi jsou z informativního charakteru naprosto dostačující, avšak z toho estetického nikoli. Kdekoho posléze napadne, jak tyto chybové hlášky přenastavit. Na tom samozřejmě není nic těžkého, stačí vhodná úprava do souboru .htaccess typu:

ErrorDocument 404 /neexistuje.html

Mít pro každý kód chyby jinou stránku je pak zbytečně komplikované. Naštěstí existuje řešení, jak různé kódy efektivně zpracovat na úrovni PHP. Základní zjednodušené nastavení souboru .htaccess pak bude vypadat obdobně:

ErrorDocument 400 http://400.dragonjake.net
# speciální hack pro 401 Authorization Required
Alias /401/ /var/www/html/
ErrorDocument 401 /401/index.php

ErrorDocument 403 http://403.dragonjake.net
ErrorDocument 404 http://404.dragonjake.net
ErrorDocument 500 http://500.dragonjake.net

Ve svém řešení využívám externí http:// adresy pro jednotlivé kódy zvlášť, v Apachi nastavené zhruba takto:

<VirtualHost *:80>
        ServerName dragonjake.net
        ServerAlias 400.dragonjake.net 403.dragonjake.net 404.dragonjake.net 500.dragonjake.net
        DocumentRoot /var/www/html
</VirtualHost>

V adresáři /var/www/html se nachází nejočekávanější soubor, skript index.php, který chybové kódy zpracovává. Klíčová je superglobální proměnná $_SERVER['REDIRECT_STATUS'], která vrací HTTP stavový kód, podle kterého následně skript rozlišuje typ chyby.

Bohužel pro chybu 500 nevrátí proměnná z nepochopitelných důvodů nic. Skript používám i jako výchozí lokaci pro požadavky, které neodchytne žádný <VirtualHost>, např. když někdo zadá přímo IP adresu do prohlížeče. Zjištění chyby jsem tedy oprasil díky URL přes regulární výraz.

if (empty($code)) {
        preg_match('~(\d+)\.dragonjake\.net$~', $_SERVER['HTTP_HOST'], $m);
        $code = $m[1];
}

Úmyslně ve své implementaci nepoužívám žádný výpis URL adresy, která se běžně u podobných chybových hlášení objevuje. Zvědavcům napovím, že se jedná o proměnné $_SERVER['REQUEST_URI'] a $_SERVER['REDIRECT_URL']. Na závěr přikládám celý kód „chybového“ skriptu.

# determine status code, 500 does not work :(
if (empty($code)) {
        preg_match('~(\d+)\.dragonjake\.net$~', $_SERVER['HTTP_HOST'], $m);
        $code = $m[1];
}

# set text by status code
switch ($code) {
        case 400:
                $h = '400 Bad Request';
                $t = 'Err... what did you say?!';
                break;
        case 401:
                $h = '401 Authorization Required';
                $t = 'You haven\'t given me a gold!';
                break;
        case 403:
                $h = '403 Forbidden';
                $t = 'Step back before you get eaten!';
                break;
        case 404:
                $h = '404 File Not Found';
                $t = 'Are you looking for something?!';
                break;
        case 500:
                $h = '500 Internal Server Error';
                $t = 'Whops! Something got wrong!';
                break;
        default:
                $h = '';
                $t = 'Server is up and running.';
}

# send header
if (!empty($h))
        header($_SERVER['SERVER_PROTOCOL'] . ' ' . $h, TRUE, $code);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="cs" lang="cs" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="content-language" content="cs" />
<meta name="author" content="Dragon Jake; e-mail: admin@dragonjake.net" />
<meta name="copyright" content="© 2006 Dragon Jake" />
<meta name="robots" content="noindex,nofollow" />
<link rel="stylesheet" type="text/css" href="/401/main.css" />
<link rel="shortcut icon" type="image/x-icon" href="/401/favicon.ico" />
<title><?php if (!empty($h)) echo $h . ' | '; ?>Dragon's Server</title>
</head>
<body>
<div id="logo"></div>
<h1><?php echo htmlspecialchars($t); ?></h1>
<a href="http://www.dragonjake.net">www.dragonjake.net</a>
</body>
</html>

Štítky:

Blog založen

Založil jsem blog, skutečně blog! Další? A proč?

Proč?

To je dobrá otázka a těžko se na ni odpovídá. Již delší dobu je můj prvotní dračí web docela mrtvý, psaní dalších článků do něj je spíše utrpením. Může za to především zastaralost kódu a jeho nejistá funkčnost, což má za následek výrazné nepohodlí něco rychle napsat. Když jsem tedy psal článek, znamenalo to minimálně 2 hodiny úmorné práce, kterou jsem raději vykonával předem do textového souboru, včetně formátování. Nakonec to dopadlo tak, že byl článek 10× delší, než jsem původně zamýšlel.

Cíle tohoto blogu

Tento blog bude čistě IT. Nebudu zde prezentovat své názory na dění ve světě, nekonečné pochody mých nesmyslných myšlenek ani zcela fiktivní a nemožné pokusy o literaturu, které jsem vlastně nikdy pořádně nezveřejnil :-)

Naopak by měl blog sloužit jako rychlé know-how pro mě a pro všechny. Budu se pokoušet o časté aktualizace kratšími blogposty ze všech možných okruhů v problematice IT, kterým se věnuji.

Textpattern?

Ano. Aniž bych měl v lásce různé Wordpressy, Joomly či Drupaly, sáhl jsem pro tentokrát po otevřeném CMS.

A teď krátká vsuvka – zadal jsem do Googlu „lehký blog texy“, protože Texy! je prostě sexy a bez ní nedokážu normálně psát a Textpattern byl jednoznačně na prvním místě. Byť byl prezentován jako lehký, po prvním seznámení mi ani zdaleka lehký nepřijde a proto se nyní vzdávám veškerých pokusů a přizpůsobení. Jsem rád, že se mi povedlo nastavit rubriky a nějaké další základní věci jsem strávil dalších několik hodin přizpůsobením designu, až jsem skončil v úpravách samotného HTML kódu a přidávání další funkcionality, přesto „to“ ještě není ono.

V dnešní době bohužel nemám přespříliš času, abych si naprogramoval vlastní, rychlejší a jednodušší blogovací systém, přestože jsem obětoval celých 30 minut vymýšlení základní struktury a funkcionality. Časem bych byť zajímavý Textpattern samozřejmě rád nahradil vlastním řešením… ale čas je silně relativní ;-)

Štítky: