Category : OS/2 Files
Archive   : OS2CMAPI.ZIP
Filename : FILEMSVR.ASM

 
Output of file : FILEMSVR.ASM contained in archive : OS2CMAPI.ZIP
.286p ;pseudo operand to enable 286 instructions
SUBTTL OS/2 Extended Edition - APPC Macro Assembler Sample Server Program
;*************************************************************************;
; ;
; MODULE NAME : FILEMSVR.ASM ;
; ;
; DESCRIPTIVE NAME : APPC FILE SERVER MASM SAMPLE PROGRAM FOR ;
; OPERATING SYSTEM/2 EXTENDED EDITION ;
; ;
; COPYRIGHT: (C) COPYRIGHT IBM CORP. 1988 ;
; LICENSED MATERIAL - PROGRAM PROPERTY OF IBM ;
; ALL RIGHTS RESERVED ;
; ;
; STATUS: LPP Release 1.1 Modification 0 ;
; ;
; FUNCTION = Receives a request for a file from a peer ;
; requester program. This program opens the file ;
; and transfers the data (or an error indicator ;
; if the file does not exist) back to the requester. ;
; ;
; Uses the following APPC verbs: ;
; ;
; RECEIVE_ALLOCATE ;
; MC_SEND_DATA ;
; MC_RECEIVE_AND_WAIT ;
; MC_SEND_ERROR ;
; MC_DEALLOCATE ;
; TP_ENDED ;
; ;
; Uses the following ACSSVC (service) Verbs: ;
; ;
; CONVERT ;
; LOG_MESSAGE ;
; ;
; MODULE TYPE = IBM Personal Computer Macro Assembler/2 ;
; (Compiles with Small Memory Model) ;
; ;
; Requires message file "APX.MSG" at runtime. ;
; ;
;=========================================================================;

IF1
INCLUDE APPC_A.INC ;APPC Include file
INCLUDE ACSSVCA.INC ;General Services include
INCLUDE SYSMAC.INC
ENDIF

@APPC MACRO VCB ;Define APPC call macro
@DEFINE APPC
@PUSHS VCB
CALL FAR PTR APPC
ENDM

@ACSSVC MACRO VCB ;Define Gen. Services call macro
@DEFINE ACSSVC
@PUSHS VCB
CALL FAR PTR ACSSVC
ENDM

MV_CVID MACRO TGT,SRC ;Define move conv_id macro
PUSH WORD PTR SRC+0
POP WORD PTR TGT+0
PUSH WORD PTR SRC+2
POP WORD PTR TGT+2
ENDM

MV_TPID MACRO TGT,SRC ;Define move tp-id macro
MV_CVID TGT+0,SRC+0
MV_CVID TGT+4,SRC+4
ENDM


DGROUP GROUP DATA

;=============================================================================
STACK SEGMENT WORD STACK 'STACK'
DW 1024 DUP (?)
STACK ENDS
;=============================================================================
DATA SEGMENT WORD PUBLIC 'DATA'

DB 'MSERVER'
DB '(C) COPYRIGHT 1987 IBM Corp.'

VERB DB ' ',0 ;APPC verb in error
PRIM_RCMSG DB ' ',0 ;Primary retcode message
SEC_RCMSG DB ' ',0 ;Secondary return code msg.
APPC_MSG_LEN EQU $-VERB

HEXTAB DB '0123456789ABCDEF' ;table for hex conversion

TP_NAME DB 'FILEMSVR ' ;64 byte name
DB 54 DUP (' ')
CNVT_LEN EQU $-TP_NAME ;length to convert to EBCDIC
;=============================================================================

SYS_ERR DB 0 ;system err flag
DOS_EOF DB 0 ;DOS file eof flag
FILENAME_OK DB 0 ;good filename flag

MSG_NO DW 0 ;message to load & display

ANB_PTR DD 0 ;AlphaNumericBuffer pointer
CONV_ID DB 4 DUP (?) ;APPC conversation id
TP_ID DB 8 DUP (?) ;APPC tp_id

BYTES_READ DW 0 ;bytes read from DOS file
FILEHANDLE DW 0 ;filehandle from DosOpen
ACTION DW 0 ;action flag from DosOpen

;============================================================================;
; VERB CONTROL BLOCK ;
;============================================================================;

RAL RCV_ALLOC <>
ORG RAL ;use 1 VCB w/ STRUCTs
TPE TP_ENDED <> ;redefined over that storage
ORG RAL
MRW MC_RCV_AND_WAIT <>
ORG RAL
MDA MC_DEALLOC <>
ORG RAL
MSD MC_SND_DATA <>
ORG RAL
MSE MC_SND_ERR <>
ORG RAL
CVT CONVERT <> ;Genl Services CONVERT
ORG RAL
LMG LOG_MESSAGE <> ;Genl Servives LOG_MESSAGE
ORG RAL
VCB DB 300 DUP (0) ;Verb Control Block
VCB_LEN EQU $-VCB ;set size of VCB


DATA ENDS

;=============================================================================

CSEG SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CSEG, DS:DGROUP


SERVER PROC FAR

PUSH DS ;prepare destination seg reg
POP ES ;for all string moves

CALL INIT_SELF ;initialize
CALL RECEIVE_ALLOCATE ;get info from incoming alloc
CALL WAIT_FOR_A_REQUEST ;wait for a request to arrive
CALL GET_SEND_CONTROL ;we will send to requester
CALL OPEN_DOS_FILE ;open the requested filename
CMP FILENAME_OK,0 ;check return code - is it good ?
JE GOOD_FILE ;if so, continue
SEND_ERR: ;else,
CALL MC_SEND_ERR ;send an error indication
JMP DONE ;and quit this request
GOOD_FILE:
CALL READ_DOSFILE ;read data from the file
CMP SYS_ERR,0 ;check return code - is it good ?
JNE SEND_ERR ;if not, send an error
CMP DOS_EOF,0 ;are we at eof ?
JNE DONE ;if so, we are finished
CALL MC_SEND_DATA ;otherwise, send the data
JMP GOOD_FILE ;and then go read some more
DONE:
CMP FILENAME_OK,0 ;did we open the file ?
JNE NEVER_OPEN ;if not, don't try to close
CALL CLOSE_DOS_FILE ;else, close the input file
NEVER_OPEN:
CALL MC_DEALLOCATE ;dealloc APPC conversation
CALL DO_TP_ENDED ;show my trans pgm has ended
@DosExit 0,0 ;We are done, exit....

;============================================================================

INIT_SELF PROC
CALL ALLOC_SHARED_BUFFER ;APPC requires an unnamed shared
;segment for a data buffer

; APPC requires the tp_name field be in EBCDIC. Convert from ASCII

CALL CLEAR_VCB ;zero the VCB
MOV CVT.OPCODE_CVT,SV_CONVERT ;opcode = CONVERT
MOV CVT.DIRECTION_CVT,SV_ASCII_TO_EBCDIC
;ASCII to EBCDIC
MOV CVT.CHAR_SET_CVT,SV_AE ;AE character set
MOV CVT.LEN_CVT,CNVT_LEN ;set the length
LEA AX,TP_NAME ;convert target addr
MOV WORD PTR CVT.SRC_PTR_CVT,AX ;srce=target says to
MOV WORD PTR CVT.TARG_PTR_CVT,AX
;convert in place
MOV WORD PTR CVT.SRC_PTR_CVT+2,DS
;set my selector
MOV WORD PTR CVT.TARG_PTR_CVT+2,DS
@ACSSVC VCB ;issue CONVERT verb
RET ;return from init_self
INIT_SELF ENDP

SHOW_MSG PROC

;logs error in MESSAGE.LOG

PUSH AX ;save reg
CALL CLEAR_VCB
MOV LMG.OPCODE_LMG,SV_LOG_MESSAGE
;verb = log_message
MOV AX,MSG_NO ;get the message number
MOV LMG.MSG_NUM_LMG,AX
MOV WORD PTR LMG.MSG_FILE_NAME_LMG,'PA'
;filename = "APX.MSG"
MOV BYTE PTR LMG.MSG_FILE_NAME_LMG+2,'X'
MOV LMG.MSG_ACTION_LMG,SV_NO_INTRV
;not immediate
@ACSSVC VCB
POP AX ;restore reg
RET ;and return
SHOW_MSG ENDP

SHOW_ERR PROC

;logs APPC error in message log

MOV AX,RAL.PRIM_RC_RAL ;get the APPC primary_retcode
LEA DI,PRIM_RCMSG ;address to CVHEX into
CALL CVHEX ;conv AX: to printable hex at DI:
MOV AX,WORD PTR RAL.SEC_RC_RAL+2
;get the APPC secondary rc
LEA DI,SEC_RCMSG
CALL CVHEX ;convert to displayable ASCII
MOV AX,WORD PTR RAL.SEC_RC_RAL
;get 2nd part of secondary_rc
LEA DI,SEC_RCMSG+4
CALL CVHEX ;convert it to printable hex
MOV AX,WORD PTR RAL.OPCODE_RAL ;get the verb opcode
LEA DI,VERB ;output line addr
CALL CVHEX ;make it printable

CALL CLEAR_VCB ;zero the VCB (NOTE-this 0s
;data from error)
MOV LMG.OPCODE_LMG,SV_LOG_MESSAGE
;opcode=log message
MOV LMG.MSG_NUM_LMG,5 ;msg #5 (APPC ERROR IN SERVER)
MOV WORD PTR LMG.MSG_FILE_NAME_LMG,'AS'
;msg file name = APX.MSG
MOV BYTE PTR LMG.MSG_FILE_NAME_LMG+2,'M'
MOV LMG.MSG_ACTION_LMG,SV_NO_INTRV
;no immediate display
MOV LMG.MSG_INS_LEN_LMG,APPC_MSG_LEN
;set insertion data len
LEA AX,VERB ;addr of ASCIIZ str.data to insert
MOV WORD PTR LMG.MSG_INS_ADDR_LMG,AX
;set address
MOV WORD PTR LMG.MSG_INS_ADDR_LMG+2,DS
@ACSSVC VCB ;issue the verb to log message
RET ;then return
SHOW_ERR ENDP

SHOW_DOS_ERR PROC

; display error in DOS call. bad rc passed in AX:

PUSH DI
PUSH AX ;save the error code
LEA DI,VERB ;output line addr
CALL CVHEX ;make it printable

CALL CLEAR_VCB ;zero the VCB
MOV LMG.OPCODE_LMG,SV_LOG_MESSAGE
;opcode=log message
MOV LMG.MSG_NUM_LMG,4 ;msg #4 (OS/2 ERROR IN SERVER)
MOV WORD PTR LMG.MSG_FILE_NAME_LMG,'AS'
;msg file name = APX.MSG
MOV BYTE PTR LMG.MSG_FILE_NAME_LMG+2,'M' ;
MOV LMG.MSG_ACTION_LMG,SV_NO_INTRV
;no immediate display
MOV LMG.MSG_INS_LEN_LMG,5 ;set insertion data len
LEA AX,VERB ;addr of ASCIIZ str.data to insert
MOV WORD PTR LMG.MSG_INS_ADDR_LMG,AX
;set address
MOV WORD PTR LMG.MSG_INS_ADDR_LMG+2,DS
@ACSSVC VCB ;issue the verb to log message
POP AX ;restore error code
POP DI
RET ;and return
SHOW_DOS_ERR ENDP

CLEAR_VCB PROC

; moves 00 to the verb control block (to reset all fields)

PUSH AX ;save existing regs
PUSH CX
PUSH DI
CLD
MOV AX, 0
MOV CX,VCB_LEN
LEA DI,VCB
REP STOSB ;zero the entire VCB
POP DI ;restore regs
POP CX
POP AX
RET ;and return
CLEAR_VCB ENDP

CVHEX PROC

; converts data in AX: into printable hex (4 bytes) at DS:DI

PUSH BX ;save work regs
PUSH CX
MOV BX,AX ;get hex data
AND BX,000FH ;cleanup to get only 1st nibble
MOV CL,HEXTAB[BX] ;lookup the ASCII representation
MOV [DI]+3,CL ;place it in the result
MOV BX,AX ;restore the original word
AND BX,00F0H ;get the 2nd nibble
MOV CL,04 ;shift it over to use as an index
SHR BX,CL
MOV CL,HEXTAB[BX] ;lookup the ASCII
MOV [DI]+2,CL ;place in result value
MOV BX,AX ;reget the original
AND BX,0F00H ;3rd nibble
MOV CL,08 ;shift to use as index
SHR BX,CL
MOV CL,HEXTAB[BX] ;lookup
MOV [DI]+1,CL ;set the result
MOV BX,AX ;reget
AND BX,0F000H ;4th nibble
MOV CL,12 ;shift
SHR BX,CL
MOV CL,HEXTAB[BX] ;lookup
MOV [DI],CL ;set result
POP CX ;restore regs & exit
POP BX
RET
CVHEX ENDP

;============================================================================;
; ;
; APPC Related Subroutines ;
; ;
;============================================================================;

RECEIVE_ALLOCATE PROC
CALL CLEAR_VCB ;init the control block to 00
MOV RAL.OPCODE_RAL,AP_RECEIVE_ALLOCATE
;set verb opcode
MOV RAL.SYNC_LVL_RAL,AP_CONFIRM_SYNC_LEVEL

CLD
LEA SI,TP_NAME
LEA DI,RAL.TP_NAME_RAL
MOV CX,64
REP MOVSB ;copy EBCDIC TP_NAME to VCB

@APPC VCB ;issue RECEIVE_ALLOCATE verb
CMP RAL.PRIM_RC_RAL,WORD PTR AP_OK
;check return code - is it good ?
JE RAL_DONE
CALL SHOW_ERR ;if not, display the retcode
MOV SYS_ERR,01 ;flag the error
@DosExit 0,0 ;and just terminate
RAL_DONE:
MV_CVID CONV_ID,RAL.CONV_ID_RAL ;get the CONVERSATION_ID
MV_TPID TP_ID,RAL.TP_ID_RAL ;get the TP_ID
RET
RECEIVE_ALLOCATE ENDP

WAIT_FOR_A_REQUEST PROC
CALL RECEIVE_AND_WAIT ;issue MC_RECEIVE_AND_WAIT
CMP SYS_ERR,0 ;check system error
JE WFR_OK
RET ;just return if a problem
WFR_OK:
MOV AX,WORD PTR MRW.WHAT_RCVD_MRW
;get the WHAT_RECEIVED field
CMP AX,WORD PTR AP_DATA_COMPLETE
;should have DATA_COMPLETE
JNE WFR_ERR1
RET ;return if ok
WFR_ERR1: ;ERR1=bad WHAT_RCVD value
MOV SYS_ERR,01 ;flag an error
CALL SHOW_ERR ;display it
@DosExit 0,0 ;and terminate
WAIT_FOR_A_REQUEST ENDP

RECEIVE_AND_WAIT PROC
CALL CLEAR_VCB
MOV MRW.OPCODE_MRW,AP_M_RECEIVE_AND_WAIT
;set verb opcode
MOV MRW.OPEXT_MRW,BYTE PTR 01 ;Mapped conversation
MV_CVID MRW.CONV_ID_MRW,CONV_ID ;CONVERSATION_ID
MV_TPID MRW.TP_ID_MRW,TP_ID ;TP_ID
MOV MRW.MAX_LEN_MRW,100 ;max length = 100
MOV AX,WORD PTR ANB_PTR+2 ;get data pointer
MOV WORD PTR MRW.DPTR_MRW+2,AX ;set selector
MOV WORD PTR MRW.DPTR_MRW,0 ;offset = 0
@APPC VCB ;issue RECEIVE_AND_WAIT verb
CMP MRW.PRIM_RC_MRW,WORD PTR AP_OK
;check return code - is it good ?
JNE RCW_ERR ;jmp if bad, else
RET ;return
RCW_ERR:
CALL SHOW_ERR ;display error msg
MOV SYS_ERR,01 ;and set error flag
@DosExit 0,0 ;and end the program
RECEIVE_AND_WAIT ENDP

MC_DEALLOCATE PROC
CALL CLEAR_VCB ;reset VCB to 0's
MOV MDA.OPCODE_MDA,AP_M_DEALLOCATE
;set verb opcode
MOV MDA.OPEXT_MDA,BYTE PTR 01 ;Mapped conversation
MV_CVID MDA.CONV_ID_MDA,CONV_ID ;CONVERSATION_ID
MV_TPID MDA.TP_ID_MDA,TP_ID ;TP_ID
CMP SYS_ERR,0 ;check system error
JNE MCD_SET_ABEND ;if so, DEALLOC ABEND
CMP FILENAME_OK,0 ;bad filename ?
JNE MCD_SET_ABEND ;if so, DEALLOC ABEND
MOV MDA.DEALLOC_TYPE_MDA,BYTE PTR AP_SYNC_LEVEL
;else normal
JMP MCD_TYPE_IS_SET
MCD_SET_ABEND:
MOV MDA.DEALLOC_TYPE_MDA,BYTE PTR AP_ABEND
;set type=ABEND
MCD_TYPE_IS_SET:
@APPC VCB ;issue DEALLOCATE verb
CMP MDA.PRIM_RC_MDA,WORD PTR AP_OK
;check return code - is it good ?
JNE MCD_ERR1
RET ;if so, return
MCD_ERR1:
CALL SHOW_ERR ;display appc error
MOV SYS_ERR,01 ;set error flag
@DosExit 0,0 ;and abend the prog
MC_DEALLOCATE ENDP

DO_TP_ENDED PROC
CALL CLEAR_VCB ;reset VCB to 00
MOV TPE.OPCODE_TPE,AP_TP_ENDED ;set opcode
MV_TPID TPE.TP_ID_TPE,TP_ID ;TP_ID
@APPC VCB ;issue TP_ENDED verb
CMP TPE.PRIM_RC_TPE,WORD PTR AP_OK
;check return code - is it good ?
JNE TPE_ERR1
RET ;if good, return
TPE_ERR1:
CALL SHOW_ERR ;display APPC error
MOV SYS_ERR,01 ;set error flag
@DosExit 0,0 ;and end the program
DO_TP_ENDED ENDP

GET_SEND_CONTROL PROC
CALL RECEIVE_AND_WAIT ;issue a MC_RECEIVE_AND_WAIT
CMP SYS_ERR,0 ;check system error
JNE GC_EXIT ;exit if any errors
MOV AX,WORD PTR MRW.WHAT_RCVD_MRW
;get the WHAT_RECEIVED field
CMP AX,WORD PTR AP_SEND ;did we get SEND CONTROL ?
JE GC_EXIT ;if so, exit & continue
MOV SYS_ERR,01 ;else set system error flag
MOV MSG_NO,17 ;'Permiss to SEND Not rcvd'
CALL SHOW_MSG ;display error msg.
GC_EXIT:
RET
GET_SEND_CONTROL ENDP

MC_SEND_DATA PROC
CALL CLEAR_VCB ;reset VCB to 0
MOV MSD.OPCODE_MSD,AP_M_SEND_DATA
;set verb opcode
MOV MSD.OPEXT_MSD,BYTE PTR 01 ;set op ext to Mapped conv.
MV_CVID MSD.CONV_ID_MSD,CONV_ID ;CONVERSATION_ID
MV_TPID MSD.TP_ID_MSD,TP_ID ;TP_ID
MOV AX,BYTES_READ ;set data length
MOV MSD.DLEN_MSD,AX
MOV WORD PTR MSD.DPTR_MSD,0 ;data addr offset = 0
MOV AX,WORD PTR ANB_PTR+2 ;get selector
MOV WORD PTR MSD.DPTR_MSD+2,AX ;set data address
@APPC VCB ;issue MC_SEND_DATA verb
CMP MSD.PRIM_RC_MSD,WORD PTR AP_OK
;check return code - is it good ?
JNE MSD_ERR1 ;if not, handle it
MSD_EXIT:
RET ;return if ok
MSD_ERR1:
CMP MSD.PRIM_RC_MSD,WORD PTR AP_DEALLOC_ABEND
;DEALLOC_ABEND ?
JE MSD_EXIT ;if so, requestor quit
;(which is o.k.)
CALL SHOW_ERR ;else, display error
MOV SYS_ERR,01 ;and set error flag
@DosExit 0,0 ;and terminate
MC_SEND_DATA ENDP

MC_SEND_ERR PROC
CALL CLEAR_VCB ;reset VCB
MOV MSE.OPCODE_MSE,AP_M_SEND_ERROR
;set verb opcode
MOV MSE.OPEXT_MSE,BYTE PTR 01 ;Mapped conversation
MV_CVID MSE.CONV_ID_MSE,CONV_ID ;CONVERSATION_ID
MV_TPID MSE.TP_ID_MSE,TP_ID ;TP_ID
@APPC VCB ;issue MC_SEND_ERR verb
CMP MSE.PRIM_RC_MSE,WORD PTR AP_OK
;check return code - is it good ?
JNE MSE_ERR1
RET ;return if ok
MSE_ERR1:
MOV SYS_ERR,01 ;set system err flag
CALL SHOW_ERR ;display error msg
@DosExit 0,0 ;and terminate
MC_SEND_ERR ENDP

;============================================================================;
; ;
; OS/2 Call Related Subroutines ;
; ;
;============================================================================;

ALLOC_SHARED_BUFFER PROC

; APPC requires a shared unnamed segment to use as a data buffer

@DosAllocSeg 4096,ANB_PTR+2,1 ;Segment is 4096 bytes long
CMP AX,0 ;check return code - is it good ?
JNE GSB_BAD ;if not, show an error
RET
GSB_BAD:
CALL SHOW_DOS_ERR ;display DOS err msg (rc in AX:)
@DosExit 0,0 ;and terminate the program
ALLOC_SHARED_BUFFER ENDP

OPEN_DOS_FILE PROC
MOV DOS_EOF,0 ;zero out the eof flag

; build the DosOpen directly (vs. using the supplied macro) since the data
; buffer with the filename is not in my data segment

@DEFINE DosOpen
PUSH WORD PTR ANB_PTR+2 ;push buffer addr selector
PUSH 0001H ;push buffer offset+1,
;to skip length byte
@PUSHS FILEHANDLE ;push address of filehandle
@PUSHS ACTION ;push address of action flag
PUSH 0 ;push doubleword = 0 = filesize
PUSH 0
PUSH 0 ;push 0 = attrib
PUSH 0001H ;open_flag = 01 = fail if no file
PUSH 0020H ;mode = 20H = read only,deny write
PUSH 0 ;push doubleword of 0s
PUSH 0
CALL FAR PTR DosOpen ;do a DosOpen

CMP AX,0 ;check return code - is it good ?
JNE BAD_OPEN ;if not, handle the error
MOV FILENAME_OK,0 ;otherwise, set good filename flag
RET ;and return
BAD_OPEN:
CMP AX,110 ;file not found ?
JE BAD_OPEN_NO_LOG ;if so, don't write to error log
CALL SHOW_DOS_ERR ;log it......
BAD_OPEN_NO_LOG:
MOV FILENAME_OK,01 ;set bad filename flag
RET
OPEN_DOS_FILE ENDP

CLOSE_DOS_FILE PROC
@DosClose FILEHANDLE ;go close the input file
CMP AX,0 ;check return code - is it good ?
JNE CDF_ERR1
RET
CDF_ERR1:
MOV SYS_ERR,01 ;set error flag
CALL SHOW_DOS_ERR ;display error msg.
@DosExit 0,0 ;and terminate
CLOSE_DOS_FILE ENDP

READ_DOSFILE PROC

; Directly build the DosRead call (since we use a buffer out of our dataseg).

@define DosRead
PUSH FILEHANDLE ;push filehandle value
PUSH WORD PTR ANB_PTR+2 ;push buffer selector address
PUSH 0 ;push offset = 0
PUSH 4096 ;push buffer length
PUSH SEG BYTES_READ ;push addr to return count to
PUSH OFFSET BYTES_READ
CALL FAR PTR DosRead ;read data from the file

CMP AX,0 ;check return code - is it good ?
JNE READ_ERR ;if not, handle the error
CMP BYTES_READ,0 ;0 = end of file
JE READ_EOF ;jmp if at eof
RET ;else return with a good read
READ_EOF:
MOV DOS_EOF,01 ;set the eof flag on
RET ;and return
READ_ERR:
CALL SHOW_DOS_ERR ;display DOS error msg
MOV SYS_ERR,01 ;set error flag on
RET
READ_DOSFILE ENDP
;=============================================================================
SERVER ENDP
CSEG ENDS
END SERVER


  3 Responses to “Category : OS/2 Files
Archive   : OS2CMAPI.ZIP
Filename : FILEMSVR.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/