C dla każdego (cz. 22.)

DOS

To już ostatni odcinek z serii o bibliotece "dos.library". Biblioteka ta zawiera masę przeróżnych funkcji. Dziś omówimy pokrótce kilka najbardziej przydatnych.

Pattern Matching - dopasowywanie do wzorca

Wzorców używamy zwykle w systemie AmigaOS do oznaczenia zbioru plików, choć nie jest to jedyne możliwe ich wykorzystanie. Przypomnijmy może jednak najpierw znaczenie znaków specjalnych, które mogą występować we wzorcach;

? - zastępuje dowolny, pojedynczy znak;
# - dowolna liczba wystąpień następującego po nim wzorca;
(ab|cd) - alternatywa wzorców;
~ - negacja logiczna;
[abc] - jeden znak ze zbioru;
[~ef] - jeden znak spośród dopełnienia zbioru [ef];
a-z - skrócony zapis zbioru znaków: oznacza znaki od "a" do "z";
% - oznacza napis pusty (przydatne np. przy alternatywie wzorców);
' - synonim "#?", czyli dowolna liczba dowolnych znaków. Symbol ten jest dostępny od systemu 2.0, ale jest standardowo wyłączony. Istnieje wiele programów, które go włączają, np. togglewc, WildStar, DOSPrefs (wszystkie można znaleźć w katalogu "utit/boot" na Aminecie). Dla tych, którzy tymi programami nie dysponują, drobna wskazówka: struktura "RootNode", pole "rn_Flags", flaga "RNF_WILDSTAR".

Kilka przykładów:

A#[a-zA-Z].(c|h) - wszystkie napisy, zawierające litery alfabetu łacińskiego, które zaczynają się od litery "A" i kończą na ".c" lub ".h".

~(#?(.o| .info)) - wszystkie napisy oprócz kończących się na ".o" lub ".info".

Istnieją dwie grupy funkcji, zajmujących się dopasowywaniem do wzorca.

Pierwsza z nich zawiera funkcje generalnego stosowania: można je wykorzystywać do dowolnych celów. Zaczynamy od "tokenizacji", czyli tłumaczenia wzorca na bardziej zrozumiałą dla algorytmu formę. Zrobią to za nas funkcje:

LONG ParsePattern( STRPTR pat, STRPTR buf, long buflen );
LONG ParsePatfcernNoCase( STRPTR pat, STRPTR but, long buflen );

Jako argument "pat" podajemy wzorzec w opisanej wcześniej formie. Efektem działania funkcji jest wpisanie przekonwertowanego wzorca do podanego jako argument "buf" bufora. Argument "buflen" określa długość bufora - powinna ona wynosić 2*strlen(pat)+2.

Funkcja zwraca -1 w wypadku błędu, O - gdy we wzorcu nie znaleziono żadnych znaków specjalnych, 1 - gdy wszystko przebiegło zgodnie z oczekiwaniami.

Funkcja ParsePatternNoCase() różni się od swojej siostry tym, że utożsamia duże i małe litery.

"Przetłumaczony" wzorzec można porównać z interesującym nas napisem za pomocą funkcji:

BOOL MatchPatternf STRPTR pat, STRPTR str);
BOOL MatchPatternNoCase( STRPTR pac, STRPTR str);

Jako argument "pat" podajemy wzorzec przetłumaczony przez, odpowiednio, ParsePattern() lub ParsePatterNoCase(). Jako argument "str" podajemy napis, który próbujemy dopasować do wzorca.

Funkcja zwraca O, gdy napis nie pasuje do wzorca lub gdy wystąpił błąd. Błąd ten oznacza zwykle brak stosu - funkcje te są wysoce rekurencyjne i powinno się im zapewnić przynajmniej 1500 bajtów stosu.

Funkcje z drugiej grupy dopasowujących do wzorca są zorientowane na operacje na plikach. Umożliwiają one filtrowane listowanie katalogów.

LONG MatchFirst( STRPTR pat, struct AnchorPath *anchor );

Funkcja MatchFirst() inicjalizuje cały proces. Jako parametr "pat" podajemy wzorzec, jako "anchor" - wskaźnik na obszar pamięci, który musi być podzielny przez 4. Struktura "AnohorPath" jest zdefiniowana w pliku "dos/dosasl.h". Należy prawidłowo zainicjować pola "ap_BreakBits" i "ap_Flags". W to pierwsze wstawiamy maski bitowe sygnałów, które mają powodować przerwanie procesu, np. S1GBREAKF_CTRL_C, aby użytkownik mógł przerwać proces, naciskając [CtrIHC]. W to drugie wstawiamy odpowiednie flagi, np. APF_DODIR, aby listowane były także wnętrza katalogów.

Funkcja zwraca (uwaga!) 0, gdy wszystko poszło dobrze, lub numer błędu (nie ma potrzeby wywoływania loErr()).

Informacje o znalezionym obiekcie pobieramy z pola "ap_Info", będącego strukturą "FileInfoBlock". Zatrzask na katalogu, w którym się ten obiekt znajduje, uzyskujemy z pola "ap_Current->an_Lock".

LONG MatchNext( struct AnchorPath *anchor );

Funkcja MatchNext() powoduje przejście do kolejnego obiektu o nazwie pasującej do wzorca. Funkcja zwraca O, gdy wszystko jest w porządku, lub numer błędu.

void MatchEnd( struct AnchorPach *anchor);

Funkcję MatchEnd() wywołujemy, gdy MatchNext() zakończy już pracę, zwracając informację o błędzie. Funkcja ta powoduje zwolnienie przydzielonych wcześniej zasobów.

Dzielenie i łączenie

Podczas używania "dos.library" niezwykle przydatne okazują się funkcje separujące nazwę katalogu od nazwy pliku oraz łączące nazwy katalogów i plików. Na pierwszy rzut oka problem wydaje się trywialny. Przy bliższym zapoznaniu się z nim okazuje się jednak, że poprawna obsługa wymaga przynajmniej kilku linijek w "C" - lepiej więc chyba zrobić to w jednej, wywołując wyspecjalizowaną funkcję.

Zacznijmy od funkcji "dzielących":

STRPTR FilePart( STRPTR path );
STRPTR PathPart( STRPTR path );

FllePart() - zwraca wskaźnik na początek ostatniego komponentu ścieżki "path", a więc zwykle nazwy pliku. Jeśli "path" składa się tylko z jednego komponentu, np. "Listing.c", to FilePart() zwróci wskaźnik na jego początek.

PathPart() - zwraca wskaźnik na znak następujący po przedostatnim komponencie napisu oznaczającego ścieżkę. Poza tym szczegółem zachowuje się tak samo, jak FilePartO. Przykłady:

Dla "SYS:Devs/DOSDrivers/Pipe" FilePart() zwraca wskaźnik na "P", a PathPartO na ostatni znak "/".

Dla"C:List" zarówno FilePart(), jak i PathPart()zwrócą wskaźnik na "L".

Do łączenia dwóch fragmentów ścieżki służy funkcja:

BOOL AddParc( STRPTR dirname, STRPTR filename, unsigned long size);

Pierwszym argumentem jest bufor zawierający początek ścieżki. Zostanie do niego dołączony fragment ścieżki wskazywany przez "filename". Rozmiar bufora podajemy przy użyciu trzeciego argumentu - "size".

Funkcja zwraca 0, gdy nastąpił błąd (zawartość bufora "dirname" nie zostanie wtedy zmieniona).

Spójrzmy na chwilę na listing 18. Wypisuje on nazwy wszystkich obiektów pasujących do podanych w linii argumentowej wzorców. Pamięć na strukturę "AnchorPath" przydzielamy z atrybutem "MEMF_CLEAR", dzięki czemu nie musimy później żadnych pól inicjować. We wzorcu do funkcji ReadArgs() występuje nie omówiony przez nas wcześniej znacznik "/M". Informuje on, że można podać więcej niż jeden napis. Adresy wszystkich napisów zostaną umieszczone w zakończonej zerem tablicy, a adres tej tablicy zostanie umieszczony we właściwym miejscu w buforze podanym przez nas jako drugi argument funkcji ReadArgs(). W listingu użyliśmy nie omówionej wcześniej funkcji:

LONG NameFromLock ( BPTR lock, STRPTR buffer, long len);

Dla podanego jako argument "lock" zatrzasku na obiekcie funkcja ta wpisuje do bufora "buffer" o długości "len" pełną nazwę obiektu wraz ze ścieżką dostępu. Funkcja zwraca 0w wypadku błędu.

Lista urządzeń

Teraz dla odmiany zajmiemy się problemem listowania zainstalo- wanych w systemie urządzeń. Spis ten jest dostępny w postaci listy elementów typu "DosList" (patrz "dos/dosextens.h"),

Nazwę urządzenia można odczytać z pola "dol_Name", które jest typu BSTR. Jest to napis rodem z BCPL, wskaźnik typu BPTR na tablicę znaków, w której pierwszy znak jest długością napisu. Do konwersji z BPTR na "normalny" wskaźnik służy makrodefinicja BADDR.

W zależności od wartości pola "dol_Type" mamy do czynienia z różnymi typami urządzeń, dla każdego z nich zawartość pola "dol_misc", będącego unią struktur, jest interpretowana inaczej.

Typ wolumen - zawiera w polu "dol_Type" wartość DLT_VOLUME, wówczas "dol_misc" należy interpretować jako strukturę "dol_volume", w której interesującym nas polem jest:

"dot_VolumeDate" - data utworzenia (inicjalizacji) dysku.

Typ przypisanie (Assign) - pole "dol_Type" równe DLT_DIRECTORY, unię interpretujemy jako strukturę "dol_assign". W tym wypadku pole "dol_Lock" zawiera zatrzask założony na katalogu, któremu jest przyporządkowane przypisanie.

Nazwę katalogu, do którego odnosi się przypisanie, możemy otrzymać z zatrzasku "dol_Lock". Gdy przypisanie odnosi się do kilku katalogów, z pomocą przychodzi pole "dol_List", które jest wskaźnikiem na jednokierunkową listę struktur "AssignList", zawierającą zatrzaski na kolejnych katalogach przypisania:

struct AssignList
{
struct AssignList *al_Next;
BPTR al_Lock;
}

Typ urządzenie (fizyczne) - wartość pola "dol_Type" wynosi DLT_DEVICE, w tym wypadku "dol_misc" jest strukturą "dol_handler". Nie będziemy jej tu szerzej omawiać, wspomnimy tylko, że pole "dol_Startup" zawiera wskaźnik BPTR na strukturę "FileSysStartupMsg". Ta z kolei struktura w polu "fssm_Environ" zawiera wskaźnik BPTR na strukturę "DosEwec", w której znajduje się wiele interesujących informacji na temat geometrii urządzenia, np. liczba cylindrów, głowic, jak również identyfikator systemu zapisu.

Wiemy już, co zawiera systemowa lista urządzeń, ale nie wiemy, skąd ją wziąć. Na to pytanie odpowiada poniższa funkcja:

struct DosList *AttemptLockDosList ( unsigned long flags );

Zwraca ona wskaźnik na poszukiwaną przez nas listę. Jej pierwszy element nie zawiera jednak ważnych danych. Gdy dostęp do listy nie jest możliwy (zazwyczaj chwilowo, np. gdy jest instalowany nowy wolumen), funkcja zwraca NULL. Argumentem tej funkcji jest tryb dostępu do listy. W naszym wypadku będzie to flaga "LDF_READ", deklarująca odczyt listy, w połączeniu z flagą określającą kierunek naszych poszukiwań: "LDF_DEVICES" - urządzenia, "LDF_VOLUMES" - wolumeny, "LDF_ASSIGNS" - przypisania,

Do poruszania się w obrębie listy służy funkcja:

struct DosList *NextDosEntry( struct DosList *dlist, unsigned long flags );

Jej argumentami są wskaźnik na uzyskany poprzednio element listy oraz typ poszukiwanego elementu.

Gdy interesuje nas konkretny element listy, zamiast szukać go samodzielnie, możemy zlecić to funkcji:

struct DosList *FindDosEntry( struct DosList *dlist, STRPTR name, unsigned long flags );

Wyszuka ona dla nas pierwszy element o wyspecyfikowanej nazwie i typie. Jeśli nic nie znajdzie, zwróci NULL. Funkcję tę można uruchomić w pętli, gdy zamierzamy znaleźć np. wszystkie wolumeny o podanej nazwie. Wielkość liter nazwy nie jest istotna, jednak należy pamiętać, że nazwa nie może zawierać znaku ":".

Gdy lista urządzeń nie jest nam już potrzebna, należy poinformować o tym system za pomocą funkcji:

void UnLockDosListf unsigned long flags );

Jej argumentem jest flaga, której wartość musi być identyczna, jak podczas przydzielania listy.

Z każdą listą udostępnianą nam przez system należy się spieszyć, ponieważ ktoś może jej potrzebować. W zasadzie można by powiedzieć, że wypisywanie listy na konsolę w listingu 19. nie jest zbyt właściwym postępowaniem. Należałoby dane zapamiętać, listę zwolnić, a dopiero później wypisać rezultaty. To jednak tylko przykład.

Listing nr 18
Listing nr 19