0288 - MS-Dos - Od środka cz.7

W kolejnym odcinku cyklu "MS-DOS" od środka omawiamy dalsze wywołania funkcji obsługi zbiorów i urządzeń zewnętrznych oraz wywołania funkcji zarządzających programami.

Funkcje obsługi zbiorów i urządzeń zewnętrznych.

Funkcja 45h - otwiera drugi kanał logiczny (handle) umożliwiający dostęp do otwartego wcześniej urządzenia lub zbioru. Wszystkie operacje wykonywane w jednym kanale będą automatycznie odwzorowane w drugim. Nowy kanał nie umożliwia więc niezależnego manipulowania zbiorem czy urządzeniem. W szczególności w przypadku zbioru, wskaźnik odczytu/zapisu w obu kanałach jest zawsze identyczny! Podczas wywołania funkcji w rejestrze BX należy umieścić numer kanału, który został przyporządkowany zbiorowi (urządzeniu) przy jego otwieraniu. Jeśli nie wystąpi żaden błąd, to system otworzy drugi kanał i poda jego numer w rejestrze AX. W przeciwnym wypadku mogą pojawić się dwa błędy: 4 - zbyt dużo otwartych zbiorów oraz 6 - zły numer kanału (kanał o podanym w BX numerze nie istnieje lub nie został otwarty).

Funkcja 46h - także otwiera drugi kanał logiczny, tym razem jednak numer drugiego kanału ustalany jest przez użytkownika, a nie przez system. Dzięki temu funkcja może być stosowana do re-definicji przypisania kanału. Użytkownik wywołując funkcje podaje numer pierwszego kanału w rejestrze BX i numer drugiego kanału w CX. Jeżeli kanał wybrany w rejestrze CX jest już wykorzystywany dla innego zbioru (urządzenia), to najpierw następuje zamknięcie tego zbioru. Możliwe błędy pojawiające się przy wywołaniu to: 4 - zbyt dużo otwartych zbiorów, 6 - zły numer kanału. Funkcję 46h można wykorzystać do zmiany standardowego przyporządkowania kanałów logicznych urządzeniom zewnętrznym. Przypuśćmy, że program wysyła dane na drukarkę (to znaczy do kanału logicznego 4). Chcąc spowodować wyprowadzenie ich do określonego zbioru należy najpierw otworzyć ten zbiór i uzyskać w BX numer przyporządkowanego zbiorowi kanału. Dla zachowania możliwości ponownego przypisania kanału 4 drukarce, należy otworzyć jeszcze jeden kanał jej przyporządkowany (funkcja 45h) i zapamiętać jego numer. Następnie do CX wpisać 4 i wywołać funkcję 46h, co spowoduje zamknięcie kanału 4 dla drukarki i otwarcie go dla nowego zbioru. Gdy dane w kanale 4 zechcemy ponownie wysłać na drukarkę, wystarczy w rejestrze BX umieścić zapamiętany na wstępie numer, a w rejestrze CX - 4, i wywołać funkcję 46h.

Funkcja 47h - podaje ścieżkę dostępu do wybranego katalogu w postaci ciągu ASCIIZ (znaki ASCII zakończone bajtem 00). Nie jest podawany symbol wybranego napędu dyskowego, ani też znak początkowy \ (backslash). Oznacza to, że gdy wybrany jest akurat katalog główny, funkcja poda pusty ciąg znaków. Rejestry DS:SI powinny zawierać adres przynajmniej 64-bajtowego obszaru pamięci, w którym system umieści ciąg ASCIIZ, zaś rejestr DL - liczbę kodującą napęd dyskowy (0 - napęd aktualny, 1 - napęd A:, itd). Może wystąpić tylko jeden błąd - 0Fh (złu numer napędu w DL). Dwie kolejne funkcję umożliwiają przeszukiwanie katalogu.

Funkcja 4Eh - służy do poszukiwania w katalogu pierwszego zbioru o podanej nazwie (może ona zawierać symbole wieloznaczne * i ?). Rejestry DS:DX powinny wskazywać na ciąg ASCIIZ określający ścieżkę dostępu do zbioru. Rejestr CL powinien specyfikować atrybuty poszukiwanego zbioru. Jeśli zbiór zostanie znaleziony, system wpisuje do aktualnego obszaru DTA 43 bajty zawierające informacje o zbiorze (tab.1). Znajduje się tam między innymi ciąg ASCIIZ będący nazwą zbioru wraz z rozszerzeniem i oddzielającą je kropką. Jeśli rozszerzenia brak, kropka nie występuje. Podanie w rejestrze CX dowolnej kombinacji bitów, określających zbiór jako hidden, system lub directory, sprawia, że poszukiwane będą zarówno zbiory o podanych atrybutach jak i atrybucie normal. Jeśli mamy atrybut : volume label, poszukiwana będzie tylko pozycja będąca etykietą. Atrybuty archiv i read only są ignorowane. Atrybuty volume label, directory i read only nie są analizowane w wersjach systemu starszych od DOS 2.00. Możliwe są dwa błędy: 2 - nie znaleziono zbioru (podano złą ścieżkę dostępu); 12h - brak dalszych zbiorów (nie znaleziono zbioru o podanej nazwie). Uwaga : w przypadku błędu, wskaźnik przeniesienia nie jest ustawiany. O błędzie świadczy niezerowa zawartość rejestru AX.

Tabela 1. informacje o znalezionym zbiorze podawane przez system w obszarze DTA po powrocie z wywołania funkcji 4Eh

Przemieszczenie Opis
00h ... 14h obszar używany przez DOS do dalszych poszukiwań (za pomocą funkcji 4Fh) - nie może być zmieniany przez użytkownika
15h atrybuty znalezionego pliku
16h ... 17h czas ostatniego dostępu
18h ... 19h data ostatniego dostępu
1Ah ... 1Dh wielkość zbioru (w bajtach; 1Ah - najmniej znaczący bajt)

Funkcja 4Fh - kontynuuje poszukiwanie zbiorów o podanej (wieloznacznej) nazwie. Korzysta z informacji wpisanej do obszaru DTA przez funkcję 4Eh (która musi zostać wcześniej wywołana) i umieszcza w tym obszarze informacje o zbiorze identycznie, jak funkcja 4Eh (tab.1). Jedyny możliwy błąd to 18 - nie znaleziono dalszych zbiorów. Uwaga : podobnie jak przy poprzedniej funkcji, wskaźnik przeniesienia nie sygnalizuje błędu.

Funkcja 56h - umożliwia zmianę nazwy zbioru. Rejestry DS:DX powinny wskazywać na starą nazwę, a ES:DI - na nową. Obie nazwy należy podać w postaci ciągów ASCIIZ, określając również ścieżki dostępu (na tym samym dysku). Jeśli obie ścieżki określają zbiory w dwóch różnych katalogach, to zbiór zostanie przepisany ze starego katalogu do nowego. Mogą wystąpić błędy: 2 - nie znaleziono zbioru, 3 - nie znaleziono ścieżki dostępu, 5 - dostęp niemożliwy i błąd 17 - podano różne napędy. Na wydruku 1 przedstawiono program wykorzystujący funkcje rozszerzone do zmiany przyporządkowania urządzeń fizycznych kanałom logicznym

Funkcje zarządzające programami

Funkcja 00h - umożliwia zakończenie programu, to znaczy przekazanie sterowania do programu wywołującego. Jest ona wywoływana bezpośrednio (INT 21h z AX = 0) lubprzez przerwanie programowe 20h. Wykorzystanie funkcji 00h nie jest jednak najlepszym sposobem kończenia programu (przynajmniej w przypadku programów typu .EXE), gdyż trzeba spełnić szereg warunków - m.in. w rejestrze CS musi być podany adres segmentu (z przesunięciem 0) zawierającego PSP (porównaj - MS-DOS od środka (2), mikroklan 6/87). Lepiej jest kończyć program przy użyciu funkcji 4Ch, gdyż po wykonaniu funkcji 00h wektory przerwań 22h, 23h i 24h ustawiane są zgodnie z adresami zapamiętanymi w obszarze PSP (przesunięcie 0Ah do 15h). A zatem, nawet jeśli zakończony program zmienił te wektory, funkcja ta przywróci ich wartości początkowe.

Funkcja 4Bh - pozwala załadować program lub podprogram do pamięci i ewentualnie go wykonać. DS:DX powinna wskazywać na ciąg ASCIIZ, opisujący ścieżkę dostępu do pliku zawierającego program, zaś para rejestrów ES:BX - na blok parametrów opisujących sposób ładowania programu (tab.3 i 3). Jeśli przy wywołaniu funkcji AL = 0, to blok wskazywany przez ES:BX składa się z 14 bajtów. Po załadowaniu podprogramu, w czasie jego wykonywania, kanały logiczne (ang. handle), otwarte przez program wywołujący, są dostępne dla podprogramu wywoływanego. Program wywołujący może zmienić przyporządkowanie kanałów logicznych urządzeniom fizycznym i w ten sposób wpływać na sposób wykonania wywoływanego podprogramu. Przed użyciem tej funkcji warto upewnić się, czy w systemie jest dostateczny obszar wolnej pamięci (funkcja 4Ah).

Tabela 2. Informacje zawarte w bloku sterującym wykorzystywanym przez funkcję 4Bh przy AL = 0

Przemieszczenie Opis
00h ... 01h adres segmentowy* ciągów ASCIIZ opisujących otoczenie (ang. environment string)
02h ... 05h adres segmentowy wiersza poleceń
06h ... 09h adres segmentowy pierwszego systemowego bloku FCB
0Ah ... 0Dh adres segmentowy drugiego systemowego bloku FCB
*) adres segmentowy oznacza 4 bajty (adres segmentu i przemieszczenie w ramach segmentu)

Tabela 3. Informacje zawarte w bloku sterującym wykorzystywanym przez funkcję 4Bh przy AL = 3

Przemieszczenie Opis
00h ... 01h adres segmentu, do którego ma być załadowany abiór (z przemieszczeniem 0)
01h ... 02h współczynnik relokacji (istotny tylko dla zbiorów typu EXE; najczęściej równy adresowi segmentu)

Do programów, które można załadować i wykonać, należy m.in. interpreter poleceń DOS. Możemy mu wskazać ciąg znaków będących poleceniem do wykonania. Z kolei to polecenie może powodować wykonanie pliku zawierającego polecenia (ang. batch file), z których ostatnim powinno być polecenie EXIT, powodujące zakończenie wykonywania interpretera poleceń. W tym momencie kontrolę przejmuje na powrót program wywołujący. Jeśli AL = 3, to nie następuje automatyczne przejście do wykonywania programu i nie jest tworzony nagłówek PSP. W ten sposób najczęściej ładuje się nakładki programowe. Jest to również prosty i efektywny sposób ładowania danych do pamięci. Uwaga : Funkcja 4Bh niszczy zawartość większości rejestrów, łącznie z rejestrami stos-owymi - SS:SP. Aby zachować wartość rejestrów po załadowaniu i wykonaniu programu, należy więc zapamiętać je w pamięci dostępnej tylko dla programu wywołującego (np. w pamięci przechowującej kod tego programu), a po zakończeniu programu odzyskać je. Użycie do tego celu stosu nie jest możliwe. Mogą wystąpić następujące błędy: 1 - zły numer funkcji, 2 - nie znaleziono zbioru, 5 - dostęp niedozwolony, 8 - zbyt mało pamięci, 10 - złe otoczenie i 11 - zły format.

Funkcja 4Ch - umożliwia w prosty sposób zakończenie programu i przekazanie kodu zwrotnego (return code) - czyli wartości, jaka przy wyjściu z programu znajduje się w rejestrze AL. Jeśli program był wywołany z pliku zawierającego polecenia (ang. batch file), wartość kodu zwrotnego można testować za pomocą operacji DOS ERROR LEVEL. Przy zakończeniu programu automatycznie zamykane są wszystkie zbiory otwarte za pomocą funkcji 3DF lub 3EH. Podobnie jak w przypadku funkcji 00h, przywracane są wektory przerwań 22h, 23h i 24h.

Funkcja 4Dh - umożliwia jednorazowe sprawdzenie kodu zwrotnego i sposobu zakończenia programu, (który przedtem załadowano i wykonano za pomocą funkcji 4Bh). System podaje w AL kod zwrotny wysłany przez program, zaś w AH - sposób zakończenia. AH = 0 oznacza zakończenie normalne, AH = 1 - zakończenie spowodowane wciśnięciem CTRL-BREAK, AH = 2 - zakończenie z powodu krytycznego błędu urządzenia, zaś AH = 3 - zakończenie przez wywołanie funkcji 31h (zakończenie programu i nie usuwanie go z pamięci).

 NAME  ATTRIB

 ; Za pomocą polecenia ATTRIB można zmieniać (ustawiać lub zerować)
 ; atrybuty. Zbiór może być niewidoczny (Hidden), lub systemowy (System),
 ; tylko do odczytu (Read only), zaznaczony jako już archiwizowany 
 ; (Archiv). Nazwy wszystkich zbiorów, których atrybuty ulegają
 ; zmianie, są wyprowadzane na ekran albo (w przypadku użycia /P na
 ; drukarkę. Jeśli operator wprowadzi polecenie w złej postaci, 
 ; a konsolę zostanie wysłany komunikat o błędzie oraz wzór 
 ; prawidłowej postaci polecenia.

 STACK SEGMENT STACK
   DW 20 DUP(?)            ; definicja segmentu stosu
 STACK ENDS

 CODE SEGMENT 'CODE'
   ASSUME CS:CODE, DS:CODE, ES:NOTHING, SS:STACK
   
   BELL             EQU 07h      ; kod ASCII sygnału akustycznego
   CHANGE_MODE      EQU 43h      ; DOS: odczyt/zapis atrybutów
   CON_OUT_HANDLE   EQU 01h      ; zdefiniowany początkowo przez DOS
                                 ; kanał wyprowadzania na konsolę
   CR               EQU 0Dh      ; kod ASCII RETURN
   DOS              EQU 21h      ; przerwanie dla wywoływania funkcji
   DUPLICATE        EQU 45h      ; DOS: włączenie drugiego kanału
   FALSE            EQU 00h      ; stała logiczna
   FIND_FIRST       EQU 4Eh      ; DOS: poszukiwanie pierwszego zbioru
   FIND_NEXT        EQU 4Fh      ; DOS: poszukiwanie kolejnego zbioru
   FORCE_HANDLE     EQU 46h      ; DOS: przełączenie kanału
   GET_DTA          EQU 2fh      ; DOS: odczyt adresu DTA (buf. dyskowego)
   LF               EQU 0Ah      ; kod ASCII LINE FEED
   PARSE            EQU 29h      ; DOS: szukanie nazwy zbioru(ów)
   PRINTER_CHANNEL  EQU 04h      ; zdefiniowany początkowo przez DOS
                                 ; kanał wyprowadzania na drukarkę
   SET_DTA          EQU 1Ah      ; DOS: ustalenie adresu bufora DTA
   TERMINATE        EQU 4Ch      ; DOS: zakończenie programu
   TRUE             EQU FFh      ; stała logiczna
   WRITE_HANDLE     EQU 40h      ; DOS: wyprowadzenie ciągu znaków

   HELP_TEXT        DB  CR,LF,'Źle wprowadzono dane!',BELL,CR,LF,LF
                    DB  'Użycie ATTRIB:',CR,LF,LF
                    DB  'ATTRIB file /A(rchiv)<+,-> /H(ide)<+,->'
                    DB  '/R(ead only)<+,-> /S(ystem)<+,-> /P(rint)'
                    DB  CR,LF
                    DB  '     + oznacza: ustawić atrybut',CR,LF
                    DB  '     - oznacza: wyzerować atrybut',CR,LF,LF,0 

   COMMANDS         DB  'A','H','P','R','S',0    ; lista poleceń

   COMMAND_MASK     DB  00100000b,00000010b,TRUE,00000001b,00000100b,0
   ; maski definiujące (za wyjątkiem /P) bity określ. poszczególne atrybuty

   NEW_LINE         DB  CR, LF     ; ciąg RETURN, LINE FEED
   ATTRIBUTE_MASK1  DB  FALSE      ; znacznik 'atrybut ma być ustawiony'
   ATTRIBUTE_MASK2  DB  FALSE      ; znacznik 'atrybut ma być wyzerowany
   COMMAND_BUFFER   DB  128 DUP(?) ; kopia wprowadzonego wiersza polecenia
   COMMAND_LENGTH   DB  ?          ; długość wiersza polecenia'
   DUMMY_FCB        DB  37 DUP(?)  ; funkcja PARSE wymaga istnienia FCB
                                   ; (który jednak nie jest tu używany)
   FILE_LENGTH      DB  ?          ; długość nazwy znalezionego zbioru
   FILE_NAME        DB  13 DUP(?)  ; bufor na nazwę zbioru
   OUT_HANDLE       DW  ?          ; komórka na zapamiętanie numeru kanału
                                   ; związanego z wyprowadzaniem
   PRINT_FLAG       DB  FALSE      ; znacznik określający miejsce wyprowadzania
                                   ; (FALSE -> CON:, TRUE -> PRN:)
   SECOND_HANDLE    DW  ?          ; miejsce na zapamiętanie numeru kanału
                                   ; związanego z konsolą - umożliwia
                                   ; przywrócenie drukowania na drukarce

   START: 
     MOV   AH, GET_DTA                ; DOS: pobranie adresu bufora dyskowego
                                      ; (znajduje się tam wiersz polecenia)
     INT   DOS
     MOV   AX, ES                     ; ustalenie adresu oryg. wiersza poleceń
     MOV   DS, AX
     MOV   SI, BX
     MOV   AX, CS                     ; ustal. adresu kopii wiersza polecenia
     MOV   ES, AX
     MOV   DI, OFFSET COMMAND_BUFFER
     MOV   CX, SIZE COMMAND_BUFFER    ; liczba znaków do skopiowania
     REP   MOVSB                      ; skopiowanie polecenia do bufora
     MOV   AX, CS                     ; przestawienie segmentu danych (DS=CS)
     MOV   DS, AX                     ; (dane są teraz w tym samym segmencie)
     MOV   AX, CON_OUT_HANDLE         ; wpisanie jako kanału wyprowadzania
                                      ; standardowego wyjścia na konsolę
     MOV   OUT_HANDLE, AX
     XOR   CH, CH
     MOV   DI, OFFSET COMMAND_BUFFER
     MOV   CL, BYTE PTR [DI]
     MOV   COMMAND_LENGTH, CL         ; zapamiętanie długości polecenia
     TEST  CL, FFh                    ; wprow. tekst po nazwie polecenia?
     CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź HELP i kończ
     INC   DI                         ; pierwszy bajt DTA zawiera liczbę
                                      ; wprowadzonych znaków
     CLD
     MOV   AL,                        ; przeskoczenie początkowych spacji
     REPE  SCASB 
     INC   CX                         ; korekcja rejestrów
     DEC   DI 
     PUSH  DI                         ; ochrona adresu pierwszego znaku
     MOV   COMMAND_LENGTH, CL         ; zapamiętanie długości ciągu znaków
     CMP   CL,0                       ; czy są jakieś znaki?
     CALL  OUTPUT_HELP                ; jeśli nie, to obsłuż błąd
     MOV   SI, DI                     ; jeśli są, to przyg. dane dla PARSE
     MOV   DI, OFFSET DUMMY_PCB 
     MOV   DX, SI                     ; oblicz adres końc. wiersza z polec.
     ADD   DX, CX
     MOV   AL, 0                      ; tryb 0 funkcji PARSE 
     MOV   AH, PARSE                  ; DOS: analiza wiersza polecenia
     INT   DOS 
     CMP   SI, DX                     ; czy były jakieś znaki? 
     CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź HELP i kończ
     CMP   BYTE PTR [DI+1]            ; zły numer napędu ?
     CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź help i kończ
     SUB   DX, SI                     ; oblicz liczbę pozostałych znaków
     MOV   CX, DX 
     XCHG  COMMAND_LENGTH, CL         ; i zapamiętaj ją 
     SUB   CL, COMMAND_LENGTH         ; oblicz długość nazwy zbioru 
     MOV   FILE_LENGTH, CL 
     POP   SI                         ; zał. adres początkowy nazwy zbioru 
     MOV   DI, OFFSET FILE_NAME       ; załaduj adres końcowy nazwy zbioru 
     REP   MOVSB                      ; przekopiuj nazwę zbioru 
     MOV   BYTE PTR [DI], 00h         ; wpisz terminator nazwy zbioru 
     MOV   DI, SI 
     MOV   CL, COMMAND_LENGTH         ; analiza pozostałych znaków 
     MOV   AL, '/'                    ; poszukiwanie '/' 
     REPNE SCASB 
     CMP   CL, 0                      ; znaleziono ?
     CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź HELP i kończ 
     MOV   SI, DI                     ; SI = DI
     PUSH  DI                         ; zapamiętaj adres ciągu poleceń 
     MOV   BX, CX 

     ; w pozost. części prog. następuje usunięcie z polecenia wszystkich spacji. 
     ; Do BX wpisuje 1. pozost. zmakow. "Małe" litery zamieniane są na "duże". 

     LOOP:
       LODSB                            ; pobranie znaku
       CMP   AL, ' '                    ; spacja?
       JZ    BLANK 
       CMP   AL, 'a'                    ; nie, czy znak ASCII < 'a'?
       JL    STORE 
       CMP   AL, 'z'                    ; nie, czy znak ASCII > 'z'?
       JG    STORE 
       AND   AL, 11011111b              ; zmień "małą" literę na "dużą"

     STORE:
       STOSB                            ; nie spacja, dołącz znak do ciągu
       JMP   END_LOOP

     BLANK:
       DEC   BX                         ; spacja, nie dołączaj do ciągu

     END_LOOP:
       LOOP  LOOP                       ; pozostań w pętli, aż nie będzie
                                        ; więcej znaków (CX=0)
       MOV   BYTE PTR[DI], '/'          ; zakończ ciąg znakiem '/' 
       CMP   BX, 0                      ; czy są dalsze polecenia?
       CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź HELP i kończ
       INC   BX                         ; dolicz '/' do liczby znaków
       POP   DI                         ; odzyskaj począt. adres ciągu znaków

     DECODE: 
       MOV   AL,[DI]                    ; pobierz znak polecenia (A,R,H,P,S) 
       PUSH  DI                         ; zapamiętaj adres znaku polecenia
       MOV   DI, OFFSET COMMANDS        ; adres ważnego polecenia 
       MOV   CX, OFFSET END_COMMANDS + 1
       SUB   CX, DI                     ; oblicz dług. tabeli poleceń + 1 
       REPNE SCASB                      ; przeszukaj tabelę poleceń 
       CMP   CX, 0                      ; znaleziono polecenie?
       CALL  OUTPUT_HELP                ; gdy nie, to wyprowadź HELP i kończ 
       POP   DI                         ; odtworzenie adresu polecenia 
       MOV   SI, OFFSET END_MASK        ; obliczenie adresu maski atrybutów 
       SUB   SI, CX 
       MOV   AL, [SI]                   ; pobranie maski atrybutów 
       CALL  SET_MASK                   ; zmienienie wskaźnika atrybutów 
                                        ; albo 'przełączenie' wy na drukarkę 
       CMP   BX, 0                      ; czy koniec ciągu poleceń? 
       JNZ   DECODE                     ; jeśli nie, to analizuj dalej 
       CALL  REDIRECT_IO                ; jeśli tak, to dla PRINT_FLAG = TRUE 
                                        ; przełącz wyprowadzanie na drukarkę 
       CALL  PROCESS_FILES              ; przełącz atrybuty
       CALL  RESTORE_IO                 ; przełącz wy z powrotem na konsolę 
       XOR   AL, AL                     ; kod powrotu = 00h 
       MOV   AH, TERMINATE              ; DOS: zakończ program 
       INT   DOS 

     OUTPUT_HELP  PROC NEAR 
     ; jeśli nie jest ustaw. znacznik zera, następuje powrót do proc głównej
     ; w przeciwnym wyp. wyprowadzany jest tekst HELP i następuje zakoń. prog.

       JNZ   NO_TEXT
       MOV   BX, OUT_HANDLE 
       MOV   CX, OFFSET END_HELP        ; obliczenie długości HELP_TEXT 
       MOV   DX, OFFSET HELP_TEXT       ; DX zawiera adr. pocz. HELP_TEXT 
       SUB   CX, DX 
       MOV   AH, WRITE_HANDLE           ; DOS: wyprowadzenie ciągu znaków 
       INT   DOS 
       XOR   AL, AL                     ; kod powrotu = 00h 
       MOV   AH, TERMINATE              ; DOS: zakończenie programu 
       INT   DOS

       NO_TEXT:
         RET                            ; powrót do programu głównego 
     OUTPUT_HELP ENDP

     SET_MASK  PROC NEAR 
     ; ustaw maskę(i) zgodnie z poleceniami
     ; /P ustawia PRINT_FLAG; + ustawia ATTRIBUTE_MASK1; - ustawia ATTRIBUTE_MASK2 

       CMP   AL, TRUE                   ; czy polecenie /P ? 
       JNZ   SET_ATTRIBUTE              ; jeśli nie, to ustaw atrybut 
       MOV   PRINT_FLAG, AL             ; jeśli tak, to ustaw znacznik drukow. 
       MOV   BP, 1                      ; dostęp następnego '/' od P 
       JMP   COMMON_SET                 ; analizuj dalsze znaki

       SET_ATTRIBUTE: 
         CMP   BYTE PTR [DI+1], '+'     ; czy po literze polecenia jest '+' ?
         JNZ   CHECK_MINUS              ; nie - sprawdź czy minus 
         OR    ATTRIBUTE_MASK1, AL      ; był '+', trzeba ustawić atrybut 
         JMP   NEXT_COMMAND
       ;
       CHECK_MINUS:
         CMP   BYTE PTR [DI+1], '-'     ; czy po literze polecenia jest '-' ? 
         JZ    RESET_ATTRIBUTE          ; był '-', trzeba wyzerować atrybut 
       ;
       OUT_HELP: 
         TEST  AL, 0                    ; ani '+', ani '-', więc wyprowadź 
         CALL  OUTPUT_HELP              ; HELP i wróć do programu głównego 
       ;
       RESET_ATTRIBUTE: 
         OR    ATTRIBUTE_MASK2, AL      ; wyzerowanie atrybutu 
       ;
       NEXT_COMMAND:
         MOV   BP, 2                    ; odstęp następnego '/' od polecenia 
       ;
       COMMON_SET: 
         CMP   BYTE PTR DS:[DI+BP], '/' 
         JNZ   OUT_HELP                 ; jeśli kolejne znaki nie zaczynają 
         INC   BP                       ; się od '/', to wyprow. tekst HELP 
         AND   DI, BP                   ; adres następnego polecenia 
         SUB   BX, BP                   ; liczba pozostałych znaków 
         RET
       ;
     SET_MASK ENDP

     REDIRECT_IO  PROC NEAR 
     ; przełączenie kanału wyprowadzania na drukarkę

       CMP   PRINT_FLAG, TRUE           ; czy wyprowadzanie na drukarkę ?
       JNZ   END_REDIRECT               ; nie - to wróć 
       MOV   AH, DUPLICATE              ; DOS: otwórz drugi kanał wyprowadz. 
       MOV   BX, OUT_HANDLE 
       INT   DOS
       JC    ERROR_HANDLE               ; jeśli błąd - to obsłuż 
       MOV   SECOND_HANDLE, AX          ; zapamiętaj numer drug. kan. wypr. 
       MOV   AH, FORCE_HANDLE           ; DOS: wymuszenie przełączenia 
       MOV   BX, PRINTER_CHANNEL        ; kanału wyprowadz. na drukarkę 
       MOV   CX, OUT_HANDLE 
       INT   DOS 
       JNC   END_REDIRECT               ; jeśli bez błędu - to wróć

       ERROR_HANDLE: 
         CALL  PROCESS_ERROR            ; obsługa błędu 
       ;
       END_REDIRECT:
         RET
       ;
     REDIRECT_IO ENDP 

     RESTORE_IO  PROC NEAR 
     ; odtworzenie połączenia logicznego kanału wyprowadzania 

       CMP   PRINT_FLAG, TRUE           ; czy wyprowadzanie na drukarkę ?
       JNZ   END_RESTORE                ; nie - to wróć 
       MOV   AH, FORCE_HANDLE           ; DOS: wymuszenie przełączania 
       MOV   BX, SECOND_HANDLE          ; kanału wyprowadzania na poprze- 
       MOV   CX, OUT_HANDLE             ; dni związany z konsolą 
       INT   DOS
       JNC   END_RESTORE                ; jeśli bez błędu - to wróć 
       CALL  PROCESS_ERROR              ; jeśli błąd - to obsłuż 

       END_RESTORE: 
         RET 
     RESTORE_IO ENDP

     PROCESS_ERROR  PROC NEAR 
     ; obsługa błędów ogranicza się do przekazania w AL kodu błędu 

       MOV   AH, TERMINATE              ; DOS: zakończenie programu 
       INT   DOS 
     PROCESS_ERROR ENDP

     PROCESS_FILES  PROC NEAR
       MOV   AH, SET_DTA                ; DOS: przełączenie bufora dyskowego 
       MOV   DX, OFFSET COMMAND_BUFFER 
       INT   DOS
       MOV   DX, OFFSET FILE_NAME       ; ustalenie adresu nazwy zbioru 
       MOV   CX, 00100111b              ; atrybuty dla funkcji poszukującej 
       MOV   AH, FIND_FIRST             ; DOS: poszukiw. pierwszego zbioru 
       INT   DOS
       JC    FIND_ERROR                 ; jeśli był błąd, to go obsłuż

       FINT_LOOP:                       ; pętla poszukiwania zbiorów
         MOV   DX, OFFSET COMMAND_BUFFER + 1Eh ; adres znalezionej nazwy zb. 
         MOV   AL, 00h                  ; funkcja: odczytanie atrybutów 
         MOV   AH, CHANGE_MODE          ; DOS: odczytanie/zapisanie atrybutów 
         INT   DOS 
         JC    CHANGE_ERROR             ; jeśli błąd, to go obsłuż
         MOV   AL, ATTRIBUTE_MASK2      ; zaneguj maskę zerowania atrybutu 
         NOT   AL 
         AND   CL, AL 
         OR    ATTRIBUTE_MASK1          ; ustawienie odpowiedniego atrybutu 
         MOV   AL, 1                    ; funkcja: zapisanie atrybutów 
         MOV   AH, CHANGE_MODE          ; DOS: odczytanie/zapisanie atrybutów
         INT   DOS
         JC    CHANGE_ERROR             ; jeśli błąd, to go obsłuż
         MOV   DI, OFFSET COMMAND_BUFFER + 1Eh
         MOV   DX, DI                   ; ustalenie adresu nazwy zbioru 
         MOV   AL, 0
         MOV   CX, 13                   ; maksymalna długość nazwy zbioru 
         REPNE SCASB                    ; poszukiwanie końca nazwy zbioru 
         DEC   DI 
         SUB   DI, DX 
         MOV   CX, DI 
         MOV   BX, OUT_HANDLE           ; wybranie kan. wypr. nazwy zbioru 
         MOV   AH, WRITE_HANDLE         ; DOS: wyprowadzenie ciągu znaków 
         INT   DOS
         MOV   DX, OFFSET NEW_LINE      ; wyprowadzenie CR,LF 
         MOV   CX, 2 
         MOV   BX, OUT_HANDLE 
         MOV   AH, WRITE_HANDLE         ; DOS: wyprowadzenie ciągu znaków 
         INT   DOS 
         MOV   AH, FIND_NEXT            ; DOS: poszukiw. następnego zbioru 
         INT   DOS
         JNC   FIND_LOOP                ; obsłuż znaleziony zbiór 
         JMP   END_SET                  ; jesli błąd to wróć
       ;
       FIND_ERROR:
       CHANGE_ERROR:
         CALL  PROCES_ERROR             ; obsługa błedu 
       ;
       END_SET:
         RET

     PROCESS_FILES ENDP

  CODE ENDS
  END START

opr. Zbigniew Pojmański
Mikroklan - Luty 1988r.