DOS2DOS
From Atariki
Protokół komunikacji ośmiobitowego Atari z pamięciami masowymi, które obsługują własny system plików, np. z innym komputerem wyposażonym w pamięci masowe i działającym pod kontrolą "obcego" systemu operacyjnego. Protokół DOS2DOS został w 2010 roku opracowany przez KMK i zaimplementowany dla SpartaDOS X 4.43 (jako sterownik PCLink) oraz SIO2BSD.
Spis treści |
Założenia
Dotychczas komunikacja Atari z pamięciami masowymi wygląda w ten sposób, że urządzenie peryferyjne rozpoznaje zestaw bardzo prostych komend, które udostępniają pamięć masową w jej stanie "surowym", tzn. można odczytać albo zapisać pojedyncze sektory i to jest właściwie wszystko. Zadaniem zorganizowania sektorów w system plików zajmuje się komputer Atari, a konkretnie DOS.
Ten system ma swoje zalety, ale kompletnie nie nadaje się do wygodnej wymiany plików pomiędzy różnymi komputerami. Np. przesłanie pliku z twardego dysku Atari na peceta oznacza na ogół nagranie go na obraz dyskietki (ATR), a następnie wyciągnięcie go stamtąd przy użyciu lokalnego oprogramowania. Przesłanie w ten sposób jednego-dwóch plików nie stanowi problemu, ale jeśli plików jest np. 1300, procedura zaczyna być uciążliwa.
Protokół DOS2DOS ma za zadanie dać Atari dostęp do systemu plików urządzenia zewnętrznego bez konieczności implementowania całej obsługi tegoż systemu plików po stronie Atari. Innymi słowy, takie urządzenie zewnętrzne z własnym dyskiem ("peceta") traktuje się jako czarną skrzynkę, która jest serwerem plików. Konkretny typ systemu plików (EXT2, FAT, NTFS, UFS) jest dla Atari obojętny, gdyż całą jego obsługę bierze na siebie serwer i jego system operacyjny. Od strony Atari potrzebny jest program-klient (w idealnym przypadku: nakładka na DOS) wysyłający do serwera polecenia odnoszące się do znajdującej się na serwerze struktury plików (typu "otwórz plik o nazwie tej a tej") i odbierający wyniki zadanych operacji.
Schemat ogólny
Protokół bazuje na protokole SIO. Wszystkie operacje plikowe realizuje się przy użyciu kombinacji trzech rozkazów SIO:
- $50 (P, jak parameters) - zapis - zainicjowanie operacji i nadanie parametrów
- $52 (R, jak results) - zapis lub odczyt - realizacja operacji, nadanie dodatkowych danych, jeśli są potrzebne, odbiór wyników
- $53 (S, jak status) - odczyt - negocjacja parametrów i kontrola stanu
Kluczowe znaczenie mają bajty DAUX1/2 bloku DCB:
- DAUX1 - wielkość bloku parametrów, jaki ma być przesłany rozkazem P (przy pozostałych rozkazach ta wartość jest ignorowana). 0 oznacza 256 bajtów.
- DAUX2 - bity 7-4: numer wersji protokołu (obecnie 0), bity 3-0: numer urządzenia.
Numer urządzenia przesyłany jest w DAUX2 po to, żeby można było komunikować się z wieloma różnymi urządzeniami plikowymi (mogą to być np. różne katalogi na serwerze plików) zajmując przy tym tylko jeden identyfikator urządzenia SIO. W bieżącej implementacji ten identyfikator to $6F (DDEVIC=$61, DUNIT=$0F). Protokół pozwala serwerowi plików go zmienić, jeśli zachodzi taka potrzeba (użytkownik sobie tego życzy).
Opcjonalnie serwer plików może rozpoznawać komendę $3F (?), SEND HIGH SPEED INDEX, jeśli komunikacja jest prowadzona przez złącze szeregowe i istnieje potrzeba przyspieszenia transmisji.
Blok parametrów
Blok parametrów przesyłany przy inicjowaniu operacji ma zmienną wielkość (wskazaną w DAUX1), gdyż różne operacje wymagają różnej ilości parametrów (np. "open" wymaga podania nazwy pliku itp., a "read" - nie).
W języku C blok ten jest zdefiniowany następująco:
struct { unsigned char fno; /* numer funkcji */ unsigned char handle; /* uchwyt pliku */ unsigned char f1,f2,f3; /* bajty pomocnicze */ unsigned char f4,f5,f5; unsigned char fmode; /* tryb otwarcia pliku */ unsigned char fatr1; /* maska atrybutów wyszukiwanych */ unsigned char fatr2; /* maska atrybutów nadawanych */ unsigned char name[12]; /* maska pliku w formacie NNNNNNNNXXX zakończona zerem */ unsigned char names[12]; /* alternatywna maska pliku w formacie jak powyżej */ unsigned char path[65]; /* ścieżka dostępu zakończona zerem */ } parbuf;
Tryb otwarcia pliku (fmode) to:
- $x4 - odczyt
- $x8 - zapis
- $x9 - dopisywanie
- $xc - wymiana danych (zapis/odczyt)
W starszym półbajcie liczy się tylko bit 0 ($1x), jeśli jest ustawiony, operacja dotyczy otwarcia katalogu jako pliku (tzw. bezpośredni dostęp do katalogu). W przeciwnym wypadku operacja dotyczy pliku.
Maska atrybutów wyszukiwanych (fatr1) wybiera atrybuty, jakie ma mieć plik, żeby serwer plików go znalazł. Poszczególne bity są zdefiniowane następująco:
# define RA_PROTECT 0x01 /* tylko zabezpieczone */ # define RA_HIDDEN 0x02 /* tylko ukryte */ # define RA_ARCHIVED 0x04 /* tylko z ustawionym atrybutem A */ # define RA_SUBDIR 0x08 /* tylko katalogi */ # define RA_NO_PROTECT 0x10 /* tylko niezabezpieczone */ # define RA_NO_HIDDEN 0x20 /* tylko nieukryte */ # define RA_NO_ARCHIVED 0x40 /* tylko ze skasowanym atrybutem A */ # define RA_NO_SUBDIR 0x80 /* tylko zwykłe pliki */
Maska $00 wybiera wszystkie pliki. Maska atrybutów nadawanych zdefiniowana jest podobnie:
# define SA_PROTECT 0x01 /* zabezpiecz */ # define SA_HIDE 0x02 /* ukryj */ # define SA_ARCHIVE 0x04 /* oznacz jako zarchiwizowany */ # define SA_UNPROTECT 0x10 /* odbezpiecz */ # define SA_UNHIDE 0x20 /* ujawnij */ # define SA_UNARCHIVE 0x40 /* oznacz jako niezarchiwizowany */
Operacje plikowe
Każda z operacji plikowych wymaga przesłania do serwera plików sekwencji komend SIO. Sterownik po stronie Atari gwarantuje przesłanie ich we właściwej kolejności. Podstawowa sekwencja wygląda następująco:
- Inicjowanie operacji: rozkaz P zapisujący blok parametrów.
- Status: rozkaz S, pozwala przejąć od serwera korektę parametrów (głównie wielkości bufora), lub przerwać procedurę w przypadku niepowodzenia i odczytać kod błędu.
- Realizacja: rozkaz R nadający lub odbierający właściwy blok danych.
- Status: rozkaz S, odczytanie końcowego statusu operacji.
Wartości DAUX1/2 we wszystkich rozkazach danej sekwencji powinny być takie same, jak dla P.
Nie każda operacja wymaga wykonania wszystkich czterech faz, spora część zadowala się pierwszymi dwiema, a nieliczne nawet tylko pierwszą.
Rozpisanie całości na tego typu fazy podyktowane jest ograniczeniami narzuconymi przez protokół SIO, który np. zasadniczo nie przewiduje przesyłania kodów błędów; SIO przesyła tylko tzw. potwierdzenia (negatywne lub pozytywne), co jest po pierwsze niewystarczające, a po drugie powoduje kłopoty, gdy sterownik SIO podejmie "samowolną" próbę powtórzenia zadanej operacji (co robi zawsze, gdy nadejdzie negatywne potwierdzenie).
Dlatego serwer plików powinien wszystko potwierdzać pozytywnie, zapisywać kod błędu do bloku statusu i liczyć na to, że program-klient odczyta go w następnym kroku. Negatywne potwierdzenie powinno nastąpić tylko wtedy, gdy komenda inicjująca jest nieprawidłowa, tzn. bajty DAUX1/2 wykazują niewłaściwy (zbyt duży) rozmiar bloku parametrów lub nieznany numer wersji protokołu.
Tabela funkcji
Zdefiniowanych jest tu 20 funkcji realizujących różne operacje na systemie plików. W rzeczywistości są to wewnętrzne funkcje kernela SpartaDOS X 4.43.
Fno | Nazwa | Przebieg operacji | Uwagi |
$00 (0) | FREAD |
|
Jest to operacja odczytu bloku danych z pliku (lub katalogu) uprzednio otwartego przez FOPEN. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. Objaśnień może wymagać narzucanie programowi-klientowi wielkości bufora przez serwer plików. Dzieje się to tylko wtedy, kiedy serwer plików stwierdza, że blok danych (plik) jest krótszy niż zadeklarowany przez klienta bufor. W każdym innym przypadku serwer "zgadza się" na wielkość zadeklarowaną przez klienta. Przesyłanie w ten sposób niewielkich porcji danych (np. pojedynczych bajtów) jest bardzo nieefektywne, program-klient powinien tego typu operacje buforować. |
$01 (1) | FWRITE |
|
Analogicznie do FREAD, jest to operacja zapisu bloku danych do pliku uprzednio otwartego przez FOPEN. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. SIO2BSD jako serwer plików nie przewiduje bezpośrednich zapisów do katalogów, próba wykonania takiej operacji skończy się błędem nr 146. Inaczej niż w przypadku FREAD, serwer plików zawsze zgadza się na deklarowaną wielkość bufora. Protokół FWRITE jest kopią FREAD po to, żeby program-klient mógł obsłużyć obie operacje jednym podprogramem. Przesyłanie w ten sposób niewielkich porcji danych (np. pojedynczych bajtów) jest bardzo nieefektywne, program-klient powinien tego typu operacje buforować. |
$02 (2) | FSEEK |
|
Ustawienie bieżącej pozycji odczytu i zapisu dla pliku (lub katalogu) otwartego przez FOPEN. Ta pozycja jest to numer bajtu w pliku, który zostanie odczytany lub zapisany jako następny. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. Dla plików otwartych do zapisu lub wymiany danych operacja powinna się zawsze udać. Dla plików otwartych do odczytu próba ustawienia pozycji odczytu poza końcem (wielkość + 1) powinna dawać błąd nr 166. |
$03 (3) | FTELL |
|
Odczytanie bieżącej pozycji odczytu i zapisu dla pliku (lub katalogu) otwartego przez FOPEN. Ta pozycja jest to numer bajtu w pliku, który zostanie odczytany lub zapisany jako następny. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. Końcowy odczyt statusu jest pomijany, gdyż ta operacja nie może się nie udać. |
$04 (4) | FILELENG |
|
Odczytanie wielkości pliku (lub katalogu) otwartego przez FOPEN. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. Końcowy odczyt statusu jest pomijany, gdyż ta operacja nie może się nie udać. |
$05 (5) | - | - | Funkcja zarezerwowana. |
$06 (6) | FNEXT |
|
Odczytanie następnego wpisu katalogu otwartego uprzednio od odczytu przez FOPEN lub FFIRST. W parbuf.handle znajduje się uchwyt pliku zwrócony przez funkcję otwierającą. Pozostałe parametry (maska plików, atrybuty poszukiwane) wg tego, co zadano funkcji otwierającej. W zwróconym wpisie katalogowym bajty nr 1 i 2 (wskaźnik do mapy sektorów pliku) są, jako bez znaczenia, zawsze wyzerowane. W przypadku, kiedy nie ma więcej wpisów pasujących do zadanych kryteriów, status jest ustawiany na 136, a R zwraca 23 zera. |
$07 (7) | FCLOSE |
|
Zamknięcie pliku (lub katalogu) otwartego przez FOPEN. W parbuf.handle znajduje się uchwyt pliku zwrócony przez FOPEN. Statusy są ignorowane, gdyż w systemie operacyjnym Atari ta operacja nie może się nie udać. |
$08 (8) | INIT |
|
Inicjowanie komunikacji z serwerem plików. Klient powinien wykonać tę funkcję raz po załadowaniu programu obsługującego protokół DOS2DOS i następny raz przy każdym ciepłym resecie. W odpowiedzi serwer plików zamyka wszystkie bieżąco otwarte pliki, inicjuje się do stanu początkowego i ustawia proponowany kod alternatywny identyfikacyjny urządzenia w bloku statusu. Program-klient może zignorować tę propozycję (serwer musi się odzywać zawsze jako CDEVIC=$6F). W przypadku jej uwzględnienia przesłany kod identyfikacyjny CDEVIC należy rozbić na wartość DUNIT i DDEVIC wg wzorów:
|
$09 (9) | FOPEN |
|
Procedura otwarcia wskazanego pliku. Z parametrów przesyłanych przez P parbuf.handle i parbuf.names są ignorowane. Uchwyt zwrócony przez R jest wartością z zakresu od 1 do 15, arbitralnie wybraną przez serwer i identyfikującą plik przy późniejszym wywoływaniu, jako parbuf.handle. W zwróconym wpisie katalogowym bajty nr 1 i 2 (wskaźnik do mapy sektorów pliku) są, jako bez znaczenia, zawsze wyzerowane. |
$0a (10) | FFIRST |
Patrz "Uwagi" |
Funkcja otwiera wskazany katalog od odczytu a następnie zwraca zawarty w nim pierwszy wpis katalogowy pasujący do zadanych kryteriów (maska plików, maska atrybutów poszukiwanych). W bieżącej implementacji, dla zaoszczędzenia pamięci po stronie Atari, FFIRST zrealizowano jako sekwencję wywołań FOPEN i FNEXT. Z tego powodu serwer plików nie ma potrzeby odróżniać tej funkcji od FOPEN. Kod serwera może jedynie chcieć (na własny użytek) poprawić przesłaną przez Atari wartość parbuf.fmode, która przy FFIRST często wynosi $04 zamiast $14. |
$0b (11) | RENAME |
|
Zmiana nazwy pliku lub katalogu. Wpisy do zmiany wyszukiwane są w katalogu parbuf.path przy użyciu maski parbuf.name, a nowa nazwa powstaje przez kombinację maski parbuf.names z nazwą pasującą do maski parbuf.name (tj. znaki z tej nazwy są wstawiane w te miejsca, na których w parbuf.names są znaki '?'). parbuf.fatr1 decyduje, czy zmiana nazwy będzie dotyczyła plików czy katalogów. Reszta parametrów (oprócz parbuf.fno) jest ignorowana. Jeśli plik o nazwie docelowej już istnieje w katalogu, operacja jest przerywana z błędem nr 151. Jeśli nie da się znaleźć pliku źródłowego, błąd ma numer 170. Zmieniane są wszystkie wpisy, które spełniają podane kryteria. |
$0c (12) | REMOVE |
|
Skasowanie pliku. Brane są pod uwagę następujące parametry: parbuf.fno, parbuf.fatr1, parbuf.name, parbuf.path. Reszta jest ignorowana. Kasowane są wszystkie pliki, których wpisy spełniają podane kryteria. |
$0d (13) | CHMOD |
|
Zmiana atrybutów wskazanego pliku. Brane są pod uwagę następujące parametry: parbuf.fno, parbuf.fatr1 (poszukiwane atrybuty), parbuf.fatr2 (nowe atrybuty), parbuf.name, parbuf.path. Reszta jest ignorowana. Zmieniane są wszystkie wpisy, które spełniają podane kryteria. |
$0e (14) | MKDIR |
|
Utworzenie katalogu. Brane są pod uwagę następujące parametry: parbuf.fno, parbuf.f1-f6 (data i czas dla katalogu), parbuf.name, parbuf.path. Reszta jest ignorowana. |
$0f (15) | RMDIR |
|
Skasowanie (pustego) podkatalogu. Brane są pod uwagę następujące parametry: parbuf.fno, parbuf.fatr1, parbuf.name, parbuf.path. Reszta jest ignorowana. |
$10 (16) | CHDIR |
|
Zmiana katalogu bieżącego. Brane są pod uwagę następujące parametry: parbuf.fno, parbuf.name, parbuf.path. Reszta jest ignorowana. |
$11 (17) | GETCWD |
|
Odczytanie ścieżki do katalogu bieżącego. |
$12 (18) | SETBOOT | Patrz "Uwagi". |
Ta funkcja służy do wskazania pliku, który zostanie uruchomiony podczas odczytu wstępnego przy starcie systemu. Ponieważ protokół DOS2DOS w bieżącej wersji nie przewiduje "bootowania" plików binarnych, SETBOOT pozostaje obecnie niezaimplementowane. |
$13 (19) | GETDFREE |
|
Odczyt informacji o dysku. Informacje ten podane są w formacie wymaganym przez bibliotekę I/O SpartaDOS X:
static uchar dfree[64] = { 0x21, /* nr wersji formatu */ 0x00, 0x00, /* wskaźnik do katalogu głównego */ 0xff, 0xff, /* całkowita liczba sektorów */ 0xff, 0xff, /* liczba wolnych sektorów */ 0x00, /* wielkość VTOC w sektorach */ 0x00, 0x00, /* numer początkowego sektora VTOC */ 0x00, 0x00, /* numer pierwszego wolnego sektora plików */ 0x00, 0x00, /* numer =="== katalogów */ 'P','C','L','i','n','k',' ',' ', /* nazwa "dysku" */ 0x00, /* liczba ścieżek */ 0x01, /* zakodowana wielkość sektora */ 0x80, /* nr wersji */ 0x00, 0x02, /* odkodowana wielkość sektora */ 0x00, 0x00, /* liczba wpisów w sektorze mapy pliku */ 0x01, /* liczba sektorów na klaster */ 0x00, 0x00, /* nr sekwencyjny i losowy */ 0x00, 0x00, /* wskaźnik ustawiany przez SETBOOT */ 0x00, /* znacznik zabezpieczenia przed zapisem */ 0,0,0,0,0,0,0,0, /* bajty zarezerwowane */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0 };
Jak widać, jedyną konkretną informacją jest tu "nazwa dysku" (volume name), która zostanie wyświetlona przez SpartaDOS w listingu katalogu. Ostatni znak jest zastępowany numerem urządzenia (przekazanym w młodszym półbajcie DAUX2) zwiększonym o $40 (czyli urządzenie nr 1 = 'A' itd.) |