wtorek, 8 listopada 2011

Troszkę o HT. Coś w tym jest.

HyperThreading jest jak publiczny kibel, w którym w jednym pomieszczeniu zainstalowano dwie muszle. Niby można napisać, że wydajność wzrosła dwukrotnie, ale w praktyce to jednak nie do końca... Często nie dość że nie jest lepiej, to jeszcze wątki potrafią dać sobie po mordzie tuż przed tym jak się całkowicie zesrają.

za pomocą Roflcopter.pl - cytat #5464.

sobota, 14 maja 2011

Przyjazna wersja emerge

Wrapper na program emerge dla Gentoo. O mnie nazywa się po prostu e .

Przydatne wywołania:
  • e a g w = aktuazliacja całego systemu, z prezentacją co będzie aktualizowane. Przy błędzie próbuje wznawiać.
  • e f w = pobranie wszystkich potrzebnych plików
  • e k = kompilacja i instalacje kernela, rekompilacja wszystkich zależnych modułów z repozytorium, generacja initrd i aktualizacja GRUBa (trzeba wywołać z katalogu z już skonfigurowanym kernelem, tj np po make oldconfig)
  • e c = odinstalowanie niepotrzebnych pakietów, usunięcie niepotrzebnych plikóœ źródłowych i plikóœ pakietów binarnych, wyczyszczenie starych kerneli i aktualizacja GRUBa. TO polecenie robić po resecie po zainstalowaniu nowego kernela.

#!/bin/bash

KEEP_GOING=""
ASK=""
VERBOSE="-v"
TREE=""
FETCH=""

while [ "$1" ]
do
    CMD=$1
    shift
    case $CMD in
        "")
            echo "$0 [Quiet] [Ask] [Tree] [Fetch] [keepGoing] [World|System|Kernel|Clean]"
            ;;
        q*|Q*)
            VERBOSE=""
            ;;

        a*|A*)
            ASK="-a"
            ;;
        t*|T*)
            TREE="-t"
            ;;
        f*|F*)
            FETCH="-f"
            ;;
            
        g*|G*)
            KEEP_GOING="--keep-going"
            ;;
            
        w*|W*)
            emerge -DuN $VERBOSE $ASK $KEEP_GOING $TREE $FETCH @world
            exit $?
            ;;

        s*|S*)
            emerge -DuN $VERBOSE $ASK $KEEP_GOING $TREE $FETCH @system
            exit $?
            ;;

        k*|K*)
            export CCACHE_DIR=/usr/src/.ccache
            export PATH=/usr/src/ccache_bin:$PATH
            [ -f .config ] && make -j8 && make modules_install install
            exit $?
            ;;

        c*|C*)
            #emerge -a --depclean && eclean-pkg -d && eclean-dist -d && eclean-kernel -a
            emerge -a --depclean && eclean-pkg -d && eclean-kernel -a
            exit $?
            ;;

        --)
            shift
            emerge $VERBOSE $ASK $KEEP_GOING $TREE $FETCH $@
            exit $?
            ;;

    esac
done


Skrypt /etc/kernel/postinst.d/10_rebuild realizujący aktualizacje po instalacji kernela:
#!/bin/sh
export FEATURES="-ccache"
sudo -H emerge -v @module-rebuild && \
sudo -H dracut -f --kver "$1" && \
sudo -H grub2-mkconfig -o /boot/grub/grub.cfg

Kompilacja systemu 32-bitowego przy wsparciu systemu 64-bitowego

Distcc jest genialnym narzędziem dla użytkowników Gentoo - umożliwia rozłożenie kompilacji programu na kilka maszyn i wybitne skrócenie czasu kompilacji. Problem z tym, że domyślnie nie działa w środowisku z różnymi architekturami.

W domu mam stacjonarne AMD64 i laptopa z P3 - to ostatnie to zdecydowanie nie demon kompilacji. W internecie jest dużo manuali jak zrobić kroskompilację (uwielbiam polski "poprawny" odpowiednik - kompilacja skrośna). Problem z tym, że nie działa mi "crossdev" - nie chce generować wszystkich potrzebnych narzędzi. Ale - po zastanowieniu się - po czorta osobne narzędzia do i686, skoro kompilator do amd64 potrafi generować kod i686 po dodaniu opcji -m32?

Przygotowanie wrappera


Na początek na komputerze z AMD64 potrzebujemy zrobić "udawaczkę" narzędzi do i686. Następujący plik umieść w katalogu /opt/cross32/bin i nazwij go i686-pc-linux-gnu-wrapper:
#!/bin/bash
exec /usr/bin/x86_64-pc-linux-gnu-g${0:$[-2]} -m32 "$@"

Nie zapomnij uczynić go wykonywalnym!
chmod a+x i686-pc-linux-gnu-wrapper

Następnie potrzebujesz zrobić parę linków:
ln -s i686-pc-linux-gnu-wrapper gcc
ln -s i686-pc-linux-gnu-wrapper g++
ln -s i686-pc-linux-gnu-wrapper c++
ln -s i686-pc-linux-gnu-wrapper cc
ln -s i686-pc-linux-gnu-wrapper i686-pc-linux-gnu-gcc
ln -s i686-pc-linux-gnu-wrapper i686-pc-linux-gnu-g++
ln -s i686-pc-linux-gnu-wrapper i686-pc-linux-gnu-c++
ln -s i686-pc-linux-gnu-wrapper i686-pc-linux-gnu-cc

wynikowo katalog /opt/cross32/bin powinien wyglądać tak:
lrwxrwxrwx 1 root root 25 kwi  8 22:35 c++ -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:35 cc -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:35 g++ -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:35 gcc -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:36 i686-pc-linux-gnu-c++ -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:36 i686-pc-linux-gnu-cc -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:36 i686-pc-linux-gnu-g++ -> i686-pc-linux-gnu-wrapper
lrwxrwxrwx 1 root root 25 kwi  8 22:36 i686-pc-linux-gnu-gcc -> i686-pc-linux-gnu-wrapper
-rwxr-xr-x 1 root root 68 kwi  8 23:03 i686-pc-linux-gnu-wrapper

Konfiguracja distcc


Idealna byłaby praca distcc bez żadnych zmian. Niestety, dużo programów jako kompilatora używa krótkich wersji (np "cc") zamiast pełnych (np. "i686-pc-linux-gnu-cc"). Po wysłaniu takiej "krótkiej" komendy na host AMD64, ten skompilowałby kod "po swojemu" (64-bitowo) i taki odesłałby na maszynę 32-bitową. Jak się domyślasz, taka kombinacja nie zadziała.

Rozwiązaniem jest modyfikacja konfiguracji distcc. Masz dwie drogi: albo rezygnujesz całkowicie z distcc 64-bitowego, albo nie :)

Obsługa tylko 32-bitów


W pliku /etc/init.d/distccd popraw zmienną PATH:
#!/sbin/runscript
# $Header: /var/cvsroot/gentoo-x86/sys-devel/distcc/files/2.18/init,v 1.1 2004/10/12 17:21:43 lisa Exp $depend() {
need net
use ypbind
}

start() {
[ -e "${DISTCCD_PIDFILE}" ] && rm -f ${DISTCCD_PIDFILE} &>/dev/null

ebegin "Starting distccd"
chown distcc `dirname ${DISTCCD_PIDFILE}` &>/dev/null
TMPDIR="${TMPDIR}" \
PATH="/opt/cross32/bin" \
/sbin/start-stop-daemon –start –quiet –startas ${DISTCCD_EXEC} \
–pidfile ${DISTCCD_PIDFILE} — \
–pid-file ${DISTCCD_PIDFILE} -N ${DISTCCD_NICE} –user distcc \
${DISTCCD_OPTS}

eend $?
}

stop() {
ebegin "Stopping distccd"
start-stop-daemon –stop –quiet –pidfile "${DISTCCD_PIDFILE}"
rm -f "${DISTCCD_PIDFILE}"
eend $?
}

Praca 64 + 32 bity


Jak pisałem, pojedyńczy demon nie obsłuży obu trybów... Trzeba więc dwóch demonów :) . Plik z poprzedniego przykładu zapisz pod nazwą /etc/init.d/distccd32 pozostawiając oryginał bez zmian. Następnie skopiuj plik/etc/conf.d/distccd do /etc/conf.d/distccd32. W oryginalnym pliku popraw linijkę z portem:
# set this option to run distccd with extra parameters
# Default port is 3632.  For most people the default is okay.
DISTCCD_OPTS="${DISTCCD_OPTS} --port 3664"

Od tej chwili masz dwa demony: distcc pracujący jako kompilacja 64-bitowa, ale na porcie 3664 oraz distcc32 pracujący jako kompilacja 32-bitowa na standardowym porcie 3632.

piątek, 13 maja 2011

iDyskTwardy

Jak pisze blog OWC, w najnowszych iMakach firma Apple poszła krok dalej w swojej specyficznej "standaryzacji" - zmieniła standard wtyczki zasilającej dysk twardy (z 4 na 7 pinów). Przy próbie podłączenia zwykłego (zgodnego ze standardami) dysku wentylatory chłodzące zatokę HDD chodzą z pełnymi obrotami niezależnie od temperatury oraz dodatkowo sprzęt nie przechodzi testu "Apple Hardware Test".

A o co poszło? Dodatkowe styki obsługują sensor temperatury na dysku. A tak dla informacji twardogłowych (pustogłowych?) z Apple - dyski twarde dostarczają informacji o swojej temperaturze przez mechanizm SMART, zgodnie z obowiązującymi standardami.

sobota, 7 maja 2011

Podłączenie Windows do drukarki udostępnionej przez CUPS

Współczesne systemy Windows potrafią bez problemu drukować bezpośrednio na serwerze CUPS (protokół IPP) bez konieczności instalowania Samby. Tutaj jest informacja, jak to zrobić.

Konfiguracja CUPSa


Zasadniczo w CUPS niewiele trzeba konfigurować. Wystarczy zezwolić na wydruk z sieci lokalnej. W konfiguracji CUPS w pliku /etc/cups/cupsd.conf wprowadź następujące zmiany (jeżeli nie istnieją) - pozwoli to na łączenie wszystkich komputerów z sieci lokalnej:

order deny,allow
deny from all
allow from 127.0.0.1
allow from @LOCAL

Po zmianach należy ponownie uruchomić serwer CUPS.

Konfiguracja Windows



  • Możesz zainstalować albo oryginalny sterownik drukarki, albo uniwersalny (choć ograniczony) sterownik CUPS.

    • Sterownik oryginalny

      • Zainstalu sobie znanymi metodami oryginalny sterownik drukarki (tak, sterowniki HP mają po kilkaset mega. Nie pytajcie mnie czemu...). Czasami instalator nalega na podłączenie drukarki - nic na to się nie poradzi.



    • Sterownik CUPS

      • Ściągnij plik Cups4Windows.zip. Rozpakuj go na pulpicie (prawy klawisz myszy na pliku archiwum, opcja Wyodrębnij wszystkie), powstanie katalog Cups4Windows\Cups for Windows.





  • Upewnij się, że możesz połączyć się z serwerem CUPS - uruchom przeglądarkę i połącz się z adresem http://IP_SERWERA_CUPS:631.

  • Doklikaj się do strony drukarki i skopiuj do schowka pełną nazwę drukarki.

  • panelu sterowania Windows wejdź w zarządzanie drukarkamidodaj drukarkę i rozpocznij dodawanie w kreatorze

  • Wybierz
    Drukarka sieciowa lub drukarka podłączona do innego komputera, kliknij 

  • Wybierz
    Podłącz do drukarki sieciowej lub biurowej

  • Jako adres wpisz http://IP_SERWERA_CUPS:631/printers/NAZWA_DRUKARKI

  • Kliknij 

  • Padnie pytanie o producenta i model drukarki, w zależności od wyboru:

    • Sterownik oryginalny/producenta

      • Wybierz odpowiedni sterownik producenta z listy, kliknij .



    • Sterownik CUPS

      • kliknij 

      • Wskaż podkatalog Cups for Windows z pulpitu jako źródło sterownika. Kliknij .

      • W nowym okienku wybierz wpis CUPS Test Driver v6, kliknij .

      • W nowym okienku z ostrzeżeniem wybierz .





  • Odpowiedz, czy to ma być drukarka domyślna, kliknij 

  • Kliknij 


Gotowe.

Network Manager przełączył się w tryb Unmanaged

Czasami program Network Manager (m.in. widoczny w zasobniku systemowym) przechodzi w tryb Unmanaged (niezarządzany). Nie można wtedy graficznie skonfigurować sieci, itp. Może być kilka tego przyczyn:

Konfiguracja NM


Jako administrator edytuj plik /etc/NetworkManager/nm-system-settings.conf. Powinien wyglądać mniej więcej tak:
[main]
plugins=ifupdown, keyfile

[ifupdown]
managed=true

Jeżeli występuje wpis managed=false lub w ogóle nie ma wpisu managed, to popraw go do postaci jak w przykładzie powyżej, następnie dalej jako administrator przeresetuj demona NM /etc/rc.d/network-manager restart.

Ręczna konfiguracja sieci


Jako administrator edytuj plik /etc/network/interfaces. Powinien wyglądać mniej więcej tak:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

Jeżeli zawiera konfiguracje jakiś interfejsów (ethXX, itp), należy je usunąć bądź zakomentowac dodając # na początku linijki. Każdy interfejs skonfigurowany w /etc/network/interfaces jest automatycznie niedostępny dla Network Managera. Po poprawkach jako administrator przeresetuj demona NM /etc/rc.d/network-manager restart.

Pozostałości w katalogu /var


Czasami zostają jakieś śmieci w katalogu /var uniemożliwiające pracę Network Managera. Poniższe komendy wysprzątają błędny wpis.
/etc/rc.d/network-manager stop
rm /var/lib/NetworkManager/NetworkManager.state
/etc/rc.d/network-manager start

Łatwy import kluczy do repozytoriów APT

Często przy dodawania nowych repozytoriów apt-get update krzyczy o brakujących kluczach GPG. Tutaj jest mały skrypt ułatwiający import tych kluczy.
#!/bin/bash

if [ ! "$1" ]
then
echo "Wywołanie: $0 klucz1 klucz2 ..."
return
fi

SERVER="hkp://wwwkeys.eu.pgp.net"
while [ "$1" ]
do
echo "Importuję klucz '$1'"
gpg --keyserver $SERVER --recv-keys $1 && gpg --armor --export $1 | apt-key add -
shift
done

Skrypt wywołuje się podając identyfikatory (szesnastkowy numer klucza podany przez apt-get) brakujących kluczy.

Triggery w PostgreSQL

Trigger definiuje się następującą składnią:
CREATE TRIGGER nazwa -- każdy trigger musi się jakoś nazywać
BEFORE albo AFTER -- czy trigger ma być wykonany przed czy po wydarzeniu
INSERT albo UPDATE albo DELETE -- których wydarzen trigger dotyczy, można połączyć kilka przez OR)
ON tabela -- trigger zawsze zakłada się na konkretną tabelę
FOR EACH
ROW albo STATEMENT -- czy trigger ma być wywołany raz na rekord, czy raz na instrukcję
EXECUTE PROCEDURE procedura(parametry);-- co ma być wywołane jako obsługa triggera

Trigger usuwa się następującą składnią:
DROP TRIGGER nazwa ON tabela -- usunięce konkretnego triggera z konkretnj tabeli

Tak więc, aby wywołać funkcję skasowano() po każdym skasowaniu rekordu z bazy rejestr należy wydać następujące polecenie:
CREATE TRIGGER mojtrigger AFTER DELETE ON rejestr FOR EACH ROW EXECUTE PROCEDURE skasowano();

Efekty uboczne w fazie BEFORE


Triggery w fazie BEFORE mogą mieć dwa różne efekty uboczne:
  • W przypadku wszystkich wydarzeń - jeżeli procedura triggera zwróci wartość NULL, to dana czynność ma zostać anulowana i dalsze triggery związane z tą czynnością nie zostaną wywołane. Często mówi się, że taki trigger wetuje jakąś operację.
  • W przypadku wydarzeń UPDATE i INSERT procedura triggera może zwrócić nową zawartość rekordu i wtedy ta właśnie wartość (a nie ta wynikająca z parametrów instrukcji UPDATE lub INSERT INTO) znajdzie się w bazie.

Macierz triggerów


wydarzenie/faza BEFORE AFTER
INSERT
  • Możliwe jest zawetowanie wstawienia.
  • Wstawiane wartości dostępne są w tablicy NEW.
  • Modyfikacja zawartości tablicy NEW powoduje wstawienie zmodyfikowanych danych.
  • W przypadku braku weta funkcja powinna zwrócić NEW.


  • Dane są już fizycznie wstawione do tabeli.
  • Wstawione dane znajdują się w tablicy NEW.

UPDATE
  • Możliwe jest zawetowanie uaktualnienia.
  • Aktualne wartości dostępne są w tablicy OLD a nowe w tablicy NEW.
  • Modyfikacja zawartości tablicy NEW powoduje wstawienie zmodyfikowanych danych.
  • W przypadku braku weta funkcja powinna zwrócić NEW.


  • Dane są już fizycznie zmodyfikowane w tabeli.
  • Poprzednie wartości dostępne są w tablicy OLD a aktualne w tablicy NEW.

DELETE
  • Możliwe jest jedynie zawetowanie usunięcia.
  • Nie ma możliwości uaktualnienia zamiast skasowania.
  • Pola kasowanego rekordu dostępne są w tablicy OLD


  • Rekord jest już fizycznie skasowany
  • Pola skasowanego rekordu dostępne są w tablicy OLD

Podstawy LDAP

Wstęp


Większość artykułów na ten temat wędruje strasznie dookoła z definicją, co to właściwie jest ten LDAP. Na początek formalna definicja: LDAP to skrót od terminu "Lighweight Directory Access Protocol", czyli "Lekki Protokół Dostępu do Usług Katalogowych". Skąd ten "lekki"? Otóż LDAP jest uproszczeniem strasznie zakręconego protokołu X.500, który opisywany jest jako "DAP". Tyle teoria - co to jest w praktyce?

Na początek LDAP można potraktować jak zwykłą bazę danych: Są w niej rekordy, w rekordach są pola z danymi, można dopisywać i kasować rekordy oraz wyszukiwać rekordy spełniające podane kryteria. Dobrze - to potrafią zwykłe bazy danych. Po co wprowadzać nowy standard? Otóż klasyczne bazy danych (Relational Database, RDB) mają dwie podstawowe cechy:

  • baza jest homogeniczna, czyli wszystkie rekordy mają tą samą strukturę (kolejność, nazwy i typy pól)

  • baza jest płaska czyli można ją zapisać rekord za rekordem jako tablicę - wszystkie rekordy są równorzędne i w podejściu klasycznym nieuporządkowane.


Opisać to można jako worek z jednakowo zbudowanymi pudełkami w środku.

W LDAP dla odmiany obie te reguły są złamane:

  • baza LDAP jest heterogeniczna, czyli każdy rekord może mieć inną strukturę - co więcej, budowa rekordu może się zmieniać w czasie

  • baza LDAP jest hierarchiczna (drzewiasta), czyli jedne rekordy mogą być rekordami podrzędnymi dla innych. Dodatkowo zamiast rekodu można umieścić odnośnik do zupełnie innego miejsca w drzewie.


Opisać to można jak matrioszkę - pudełka w pudełkach, a każde może być inaczej zbudowane.

Adresowanie rekordów


Ze względu na różną reprezentację "logiczną" baz, w RDB i LDAP różnie adresuje się rekordy.

W RDB do adresowania rekordów służy tzw. klucz główny - specjalna kolumna zawierająca wartość unikalną i różną dla każdego rekordu (często stosowany jest numer kolejny rekordu w bazie). Czyli adres rekordu to np. "1158".

W LDAP też można zasymulować klucz główny. Można zrobić pole rekordu z unikalnym numerem i według niego szukać, ale nie o to przecież chodzi. LDAP do wskazania rekordów wykorzystuje ścieżkę do rekordu (distinguished name, DN). W obrębie jednego poziomu hierarchii (jednego rekordu nadrzędnego) stosowany jest skrót, nazwa rekordu (relative distinguished name, RDN). Żeby było bardziej obrazowo - LDAP zachowuje się jak dysk: rekordy to pliki, DN to absolutna ścieżka do pliku a RDN to względna ścieżka do pliku. Adresy rekordów LDAP są dłuższe od kluczy RDB, jednak sa bardziej obrazowe: "cn=Kowalski,ou=Kadry,dc=Firma,dc=pl". Jak zmrużycie oczy, to wygląda to trochę jak nazwa domenowa ('kowalski.kadry.firma.pl'). Jak to się czyta?

Podana zazwa składa się tutaj z 4 części, czytanych od prawej do lewej i oddzielonych przecinkami. Każda część ma postać typ=nazwa. Typ określa charakter danej opisanej nazwą:

  • cn - nazwa rekordu (od common name) - to jest najbliższe kluczowi głównemu.

  • dc - fragment adresu DNS podmiotu opisanego DN, czyli firma.pl staje się dc=Firma,dc=pl (od directory context)

  • o - nazwa firmy (od organization)

  • ou - oddział firmy (od organizational unit)

  • c - kraj (od country)

  • l - miasto (od locality)


Ostatnia część DN, wspólna dla wszystkich rekordów w bazie LDAP to tzw adres bazowy (base distingushed name). Może być konstruowany na kilka sposobów:

  • od adresu DNS firmy: dc=Firma,dc=pl (forma preferowana) bądź o=firma.com (forma przestarzała)

  • od nazwy firmy: o=Super Firma,c=pl

  • od lokalizacji geograficznej: o=Super Firma,l=Szczecin,st=Zachodniopomorskie,c=pl


Przykład


Zasadniczą różnicę między LDAP a RDB pokażę w dekompozycji uproszczonego problemu pod tytułem faktura VAT (moi studenci z baz danych już pewnie mają gęsią skórkę na karku). Żeby zbytnio nie przeciągąć:

RDB



  • Faktura jest rekordem w tabeli FAKTURY zawierającym dane faktury, jako całości: datę, numer, forma płatności, dane kontrahenta, itd.

  • Faktura ma kolejne wpisy umieszczone w tabeli SZCZEGÓŁY. Każdy wpis zawiera towar, ilość, cenę, podatek, numer wpisu na fakturze i numer faktury do której należy.


Jak widać, w tym super-duper-uproszczonym przypadku mamy już dwie tabele, przy realizacji praktycznej trzeba jeszcze zrobić tabele z kontrahentami, produktami, cenami, stawkami VAT, itp - w ogólności należy przeprowadzić normalizację bazy danych.

LDAP



  • jest drzewo ogólnych danych firmy dc=Firma,dc=pl

  • jest tam poddrzewo 'faktury' ou=faktury,dc=Firma,dc=pl

  • każda faktura jest rekordem zawierającym dane faktury - datę, numer, dane kontrahenta, itd. cn=FV01/06,ou=faktury,dc=Firma,dc=pl

  • szczegóły faktury są podrekordami danej faktury: zawierają towar, ilość, cenę, podatek cn=1,cn=FV01/06,ou=faktury,dc=Firma,dc=pl


Jak widać faktura w RDB jest rozsmarowana po wielu rekordach co najmniej dwóch tabel a w LDAP jest zebrana jako jeden rekord z przynależnymi mu podrekordami (tzw. poddrzewo).

Usuwanie nieaktualnych kerneli z Ubuntu i Debiana

Poniżej znajduje się skrypt automatycznie czyszczący nieaktualne wersje kernela i pakietów "okołokernelowych" (nagłówki, itd) z działającego systemu. Skrypt należy uruchomić po aktualizacji zmieniającej kernel na nowszy i po zresetowaniu komputera. Kluczową sprawą jest reset, gdyż starszą wersję można bezpiecznie wywalić tylko z poziomu działania nowej wersji kernela.
#!/bin/bash

CURRENT=`uname -r | sed 's/-[a-z0-9]*$//g'`
OLD=""

cd /boot

for VM in vmlinuz*
do
OLD_VM=`echo "$VM" | sed 's/vmlinuz-\|-[a-z0-9]*$//g'`
if [ "$CURRENT" != "$OLD_VM" ]
then
echo "Nieużywany kernel $OLD_VM - do usunięcia"
OLD="$OLD '~n$OLD_VM'"
else
echo "Bieżący kernel $CURRENT - zostaje na dysku"
fi
done

if [ "$OLD" ]
then
aptitude purge $OLD
fi