OPT REG=5
OPT PREPROCESS
OPT OSVERSION=37

/*
 * MS-Dos partitiontable lister and Cross-Dos mounter for PC-HD's.
 *
 *
 * v1.0: (c) K.P. van Beem (patrick@aobh.xs4all.nl or 2:280/464.2)
 * v1.1: (c) Harry "Piru" Sintonen (sintonen@iki.fi)
 *
 * Based on information gained from the source of the Linux FDisk, by
 * A. V. Le Blanc (LeBlanc@mcc.ac.uk).
 * This program is free software.  You can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * Version 1.0, 28-1-96, Initial version.
 * Version 1.1, 31-7-00, Fixed tempfile problems. Fixed QUIET-option
 * related memory loss. Blindly assumed String() always succeed,
 * crashed if it didn't. Added custom error message for
 * 'No disk in the drive' and 'Block len not supported' ioerr cases.
 */

#define VERSION '$VER: MountDos 1.1 (31.7.00) PD, (c) Harry "Piru" Sintonen.\n'

MODULE 'devices/trackdisk', 'exec/io', 'exec/ports'
MODULE 'dos/dos', 'dos/dostags'
MODULE 'utility/tagitem'
MODULE 'icon', 'workbench/workbench', 'workbench/startup'



/******************* Globals **********************/

#define TEMPLATE      'DEVICE,UNIT/N,PREFIX,LIST/S,GENERATE/S,MOUNT/S,QUIET/S'
#define ARG_DEVICE    arg_ptrs[0]
#define ARG_UNIT      arg_ptrs[1]
#define ARG_PREFIX    arg_ptrs[2]
#define ARG_LIST      arg_ptrs[3]
#define ARG_GENERATE  arg_ptrs[4]
#define ARG_MOUNT     arg_ptrs[5]
#define ARG_QUIET     arg_ptrs[6]
#define NUMARGS                7

#define EXTENDED          5
#define SECTOR_SIZE       512
#define PART_TABLE_FLAG   $55AA
-> Actually $AA55, but ix86's are realy weird. They swap multiple-byte values.

-> Byte values. Highest two bits of sector are added to cylinder.
#define SECTOR(s)     (s AND $3F)
#define CYLINDER(s,c) (c OR Shl(s AND $C0,2))


-> This is how an MS-Dos partition entry looks like:
OBJECT partition
  boot_ind    :CHAR   -> 0x80 - active
  head        :CHAR   -> starting head
  sector      :CHAR   -> starting sector
  cyl         :CHAR   -> starting cylinder
  sys_ind     :CHAR   -> What partition type
  end_head    :CHAR   -> end head
  end_sector  :CHAR   -> end sector
  end_cyl     :CHAR   -> end cylinder
  start_sect  :LONG   -> starting sector counting from 0
  nr_sects    :LONG   -> nr of sectors in partition
ENDOBJECT

-> Global return-code
DEF rc       = RETURN_OK            -> Exceptionhandler will correct this.

-> Program arguments
DEF rargs    = NIL
DEF arg_ptrs = NIL : PTR TO LONG
DEF device, unit                    -> Device and unit we work on
DEF prefix   = ""

-> Device handling
DEF tio      = NIL :PTR TO ioexttd  -> Trackdisk io blok.

-> Count number of partitions found, for unique naming of mount-entries
DEF dosparts, ndosparts


/****************** Automatic exceptions **********************/
-> What can go wrong...
RAISE "CTLC"  IF CtrlC()             = TRUE,
      "DEV"   IF OpenDevice()       <> NIL,
      "DOS"   IF ReadArgs()          = NIL,
      "IO"    IF DoIO()             <> NIL,
      "LIB"   IF OpenLibrary()       = NIL,
      "RES"   IF CreateIORequest()   = NIL,
      "RES"   IF CreateMsgPort()     = NIL,
      "STAC"  IF FreeStack()         < 1000



/****************** Main prog **********************/

PROC main() HANDLE

  -> Device handling
  DEF msgport = NIL                 -> MsgPort for the trackdisk.device
  DEF topen   = FALSE               -> Flag if the trackdisk.device is opened.

  -> WB args
  DEF arg :PTR TO wbarg, dobj :PTR TO diskobject, wm :PTR TO wbstartup
  DEF olddir, item

  DEF tmp

  -> Check the parameters we got, first, initial and default values.
  NEW arg_ptrs[NUMARGS]
  device := 'scsi.device'
  IF wbmessage
    -> Started from workbench
    OpenLibrary('icon.library', 37)
    wm        := wbmessage
    arg       := wm.arglist
    IF wm.numargs > 1 THEN arg++        -> We are called with a project.
    olddir    := CurrentDir(arg.lock)
    dobj      := GetDiskObject(arg.name)
    IF dobj
      IF FindToolType(dobj.tooltypes,'LIST')     THEN ARG_LIST     := TRUE
      IF FindToolType(dobj.tooltypes,'GENERATE') THEN ARG_GENERATE := TRUE
      IF FindToolType(dobj.tooltypes,'MOUNT')    THEN ARG_MOUNT    := TRUE
      IF FindToolType(dobj.tooltypes,'QUIET')    THEN ARG_QUIET    := TRUE

      item := FindToolType(dobj.tooltypes,'DEVICE')
      IF item
        tmp := String(StrLen(item))
        IF tmp
          StrCopy(tmp, item)
          device := tmp
        ELSE
          device := 'out_of_memory'
        ENDIF
      ENDIF

      item := FindToolType(dobj.tooltypes,'UNIT')
      IF item THEN unit := Val(item)

      item := FindToolType(dobj.tooltypes,'PREFIX')
      IF item
        tmp := String(StrLen(item))
        IF tmp
          StrCopy(tmp, item)
          prefix := tmp
        ENDIF
      ENDIF

      FreeDiskObject(dobj)
    ENDIF
    CurrentDir(olddir)
    -> be careful, code above must not throw exception
    CloseLibrary(iconbase)
  ELSE
    -> Started from CLI
    rargs  := ReadArgs(TEMPLATE, arg_ptrs, NIL)
    IF ARG_DEVICE THEN device := ARG_DEVICE
    IF ARG_UNIT   THEN unit   := Long(ARG_UNIT)
    IF ARG_PREFIX THEN prefix := ARG_PREFIX
  ENDIF

  -> No options?
  IF (ARG_GENERATE=0) AND (ARG_MOUNT=0) AND (ARG_LIST=0)
    PrintF(VERSION+6)
    PrintF('Type: ''MountDos ?'' for an option summary.\n')
    Raise(0)
  ENDIF

  -> Redirect output if option quiet is specified
  IF (ARG_QUIET = TRUE)
    tmp := Open('NIL:', MODE_NEWFILE)
    IF tmp
      -> bugfix: Old conout was never closed in v1.0
      Close(conout)
      conout := stdout := tmp
    ENDIF
  ENDIF

  -> Trackdisk initialisation
  msgport := CreateMsgPort()
  tio     := CreateIORequest(msgport, SIZEOF ioexttd)
  OpenDevice(device, unit, tio, 0)
  topen := TRUE

  -> Actions to perform <-

  IF ARG_LIST
    list_table()
  ENDIF

  IF ARG_GENERATE
    dosparts := ndosparts := 0
    generate(stdout)
  ENDIF

  IF ARG_MOUNT
    mount()
  ENDIF


EXCEPT DO

  /* Inspect the error (if any) */
  IF exception
    rc := RETURN_FAIL
    SELECT exception
      CASE "DEV"
        PrintF('Unable to open device ''\s'', unit \d.\n', device, unit)
      CASE "DOS"
        PrintFault(IoErr(), NIL)
      CASE "CTLC"
        PrintF('Ctrl-C detected.\n')
      CASE "IO"
        tmp := tio.iostd.error
        SELECT tmp
          CASE $fc  -> -4
            PrintF('Block len not supported (CD-ROM?)\n')
          CASE TDERR_DISKCHANGED
            PrintF('No disk in the drive\n')
            rc := RETURN_WARN
          DEFAULT
            PrintF('Device IO-error $\h.\n', tio.iostd.error)
        ENDSELECT
      CASE "LIB"
        PrintF('Unable to open a needed library.\n')
      CASE "MEM"
        PrintF('Unable to allocate memory.\n')
      CASE "RES"
        PrintF('Unable to allocate needed resources.\n')
      CASE "STAC"
        PrintF('Stack overflow.\n')
      DEFAULT
        PrintF('Unknown exception: $\h.\n', exception)
    ENDSELECT
  ENDIF

  /* Clean-up */
  IF topen          THEN CloseDevice    (tio)
  IF tio            THEN DeleteIORequest(tio)
  IF msgport        THEN DeleteMsgPort  (msgport)

  IF rargs          THEN FreeArgs       (rargs)

ENDPROC rc



/************ Read a partition-table block from disk *************/
PROC readblock(buffer, block)

  tio.iostd.command   := CMD_READ
  tio.iostd.data      := buffer
  tio.iostd.length    := SECTOR_SIZE
  tio.iostd.offset    := Mul(block,SECTOR_SIZE)
  tio.iostd.flags     := IOF_QUICK
  DoIO(tio)

  -> Check table ID
  IF Int(buffer + $01FE) <> PART_TABLE_FLAG
    PrintF('WARNING: Invalid tabel flag $\z\h[4].\n', Int(buffer + $01FE))
    rc := RETURN_WARN
  ENDIF

ENDPROC



/************ Auto-mount dos-partitions ************
 * Generate the mountlist-entries in a file and call the mount
 * command to mount each dos-partition in it.
 */
PROC mount() HANDLE
  DEF cnt, tmpfh = 0, tmp, tmpname[20] :STRING
  DEF command[80] :STRING

  -> Generate `random' temp filename. Prevent clash if
  -> multiple mountdos instances are run simultanously.
  StringF(tmpname, 'T:MDTML\h', FindTask(NIL))

  tmpfh := Open(tmpname, MODE_NEWFILE)
  IF tmpfh = NIL
    PrintF('Unable to create a temporary mountlist.\n')
    rc := RETURN_ERROR
    RETURN
  ENDIF

  dosparts := ndosparts := 0
  generate(tmpfh, 0, TRUE)
  Close(tmpfh)
  tmpfh:=0

  FOR cnt := 1 TO dosparts STEP 1
    CtrlC()                       -> User may want to cancel the mount-procedure
    PrintF('Mounting \sMD\d\d:\n', prefix, unit, cnt)
    StringF(command,'Mount \sMD\d\d: FROM \s', prefix, unit, cnt, tmpname)
    tmp := SystemTagList(command, [SYS_OUTPUT, stdout, TAG_DONE])
    IF tmp > rc THEN rc := tmp    -> Copy highest return-code
  ENDFOR

EXCEPT DO

  -> bugfix: In v1.0 if exception was raised inside generate()
  -> the temporaty mountlist filehandle was left unclosed. This
  -> prevented the DeleteFile() call from succeeding, and
  -> additionally caused further MountDos MOUNT calls to fail
  -> since Open('T:MountDosTmpMountlist',MODE_NEWFILE) failed.
  IF tmpfh
    Close(tmpfh)
  ENDIF

  DeleteFile(tmpname)
  ReThrow()

ENDPROC



/************ Generate mountlist-entries for partition chain ************/
-> 'dosonly' indicates only dos-partitions should be generated.

PROC generate(fh, block=0, dosonly=FALSE)
  DEF cnt, bpt, lowcyl, highcyl, isdos

  -> Partition table
  DEF buffer[SECTOR_SIZE] : ARRAY OF CHAR
  DEF p:PTR TO partition   -> Pointer to the partition table


  -> Recursive function, check Ctrl-C and stack (uses auto-exceptions).
  FreeStack()
  CtrlC()

  -> Read partition-table block
  readblock(buffer, block)
  p := buffer + $01BE

  FOR cnt := 1 TO 4 STEP 1

    -> Swap 'Intel'-values:
    p.start_sect := swapL(p.start_sect)
    p.nr_sects   := swapL(p.nr_sects)

    -> Can this partition be mounted with CrossDos or not?
    IF (p.sys_ind=1) OR (p.sys_ind=4) OR (p.sys_ind=6)
      INC dosparts
      isdos := TRUE
    ELSE
      INC ndosparts
      isdos := FALSE
    ENDIF

    IF  (p.sys_ind <> 0) AND (p.sys_ind <> EXTENDED) AND
    ( (dosonly AND isdos) OR Not(dosonly) ) 
      lowcyl  := p.start_sect + block
      highcyl := lowcyl + p.nr_sects + block
      lowcyl, highcyl, bpt := smaller(lowcyl, highcyl)
      VfPrintf(fh,
        '\s\s\d\d:\n'+
        'FileSystem       = \s\n'+
        'Device           = \s\n'+
        'Unit             = \d\n'+
        'Surfaces         = 1\n'+
        'BlocksPerTrack   = \d\n'+
        'Reserved         = 0\n'+
        'LowCyl           = \d\n'+
        'HighCyl          = \d\n'+
        'BufMemType       = 0\n'+
        'Priority         = 10\n'+
        'GlobVec          = -1\n'+
        'DosType          = \s\n'+
        'Activate         = 1\n'+
        '#\n\n',
        [
        prefix,
        IF isdos THEN 'MD'                    ELSE 'ND', unit,
        IF isdos THEN dosparts                ELSE ndosparts,
        IF isdos THEN 'L:CrossDOSFileSystem'  ELSE systype(p.sys_ind),
        device, unit, bpt, lowcyl, highcyl - 1,
        IF isdos THEN '0x4D534400'            ELSE '?'
        ]:LONG
      )
    ENDIF

    IF (p.sys_ind = EXTENDED) AND (p.start_sect <> 0)
      generate(fh, p.start_sect + block, dosonly)
    ENDIF

    p++
  ENDFOR

ENDPROC



/************ Shows the contents of an partition chain. ************/

PROC list_table(block=0)
  DEF cnt

  -> Partition table
  DEF buffer[SECTOR_SIZE] : ARRAY OF CHAR
  DEF p:PTR TO partition   -> Pointer to the partition table


  -> Recursive function, check Ctrl-C and stack (uses auto-exceptions).
  FreeStack()
  CtrlC()

  -> Read partition-table block
  readblock(buffer, block)
  p := buffer + $01BE

  PrintF('      Start:         End:             Start  Number of\n'+
         ' boot Head Cyl. Sect Head Cyl. Sect   Sector Sectors     MB System\n')
  FOR cnt := 1 TO 4 STEP 1
    -> Swap 'Intel'-values:
    p.start_sect := swapL(p.start_sect)
    p.nr_sects   := swapL(p.nr_sects)
    PrintF('\d[1] \s \d[4]\d[5]\d[5] \d[4]\d[5]\d[5] \d[8] \d[9] \d[4] \s\n',
      cnt,        IF (p.boot_ind) THEN 'yes' ELSE 'no ',
      p.head,     CYLINDER(p.sector, p.cyl),         SECTOR(p.sector),
      p.end_head, CYLINDER(p.end_sector, p.end_cyl), SECTOR(p.end_sector),
      IF p.start_sect = 0 THEN 0 ELSE p.start_sect + block, p.nr_sects,
      -> bugfix: would have overflown on v1.0 with >64g disk. Hehe :-)
      Div(p.nr_sects,($100000/SECTOR_SIZE)), systype(p.sys_ind)
      )
    p++
  ENDFOR

  -> Check for extended partitions.
  p := buffer + $01BE
  FOR cnt := 1 TO 4 STEP 1
    IF p.sys_ind = EXTENDED
      IF p.start_sect = 0
        PrintF('Bad offset in extended partition number \d.\n', cnt)
      ELSE
        -> Recursive call of this funtion.
        PrintF('\nExtended partition at sector \d:\n', p.start_sect + block)
        list_table(p.start_sect + block)
      ENDIF
    ENDIF
    p++
  ENDFOR

ENDPROC



/********** System types of partitions *****************/
PROC systype(t)
  DEF stypes: PTR TO LONG

  stypes := [
    0, 'Empty',
    1, 'DOS 12-bit FAT',
    2, 'XENIX root',
    3, 'XENIX usr',
    4, 'DOS 16-bit <32M',
    EXTENDED, 'Extended',
    6, 'DOS 16-bit >=32M',
    7, 'OS/2 HPFS',         -> or QNX?
    8, 'AIX',
    9, 'AIX bootable',
    10, 'OPUS',
    $40, 'Venix 80286',
    $51, 'Novell?',
    $52, 'Microport',       -> or CPM?
    $63, 'GNU HURD',        -> or System V/386?
    $64, 'Novell',
    $75, 'PC/IX',
    $80, 'Old MINIX',       -> Minix 1.4a and earlier
    $81, 'Linux/MINIX',     -> Minix 1.4b and later
    $82, 'Linux swap',
    $83, 'Linux native',
    $93, 'Amoeba',
    $94, 'Amoeba BBT',      -> (bad block table)
    $b7, 'BSDI fs',
    $b8, 'BSDI swap',
    $c7, 'Syrinx',
    $db, 'CP/M',            -> or Concurrent DOS?
    $e1, 'DOS access',
    $e3, 'DOS R/O',
    $f2, 'DOS secondary',
    $ff, 'BBT'              -> (bad track table)
  ]

  WHILE (t <= $FF) AND (t > stypes[])
    stypes++
    stypes++
  ENDWHILE

ENDPROC IF (t = stypes[]) THEN stypes[1] ELSE 'Unknown'



/*** Some assembler routines for convenience... ***/



/******** Lower the values of lowcyl and highcyl. ********
 * Some file-systems/devices can't handle high values for the cylinders
 * (probably higher than 65536). So we try to keep them low, by increasing
 * the number of cylinders.
 */

PROC smaller(lowcyl, highcyl)
  DEF     bpt

  MOVE.L  lowcyl,D0
  MOVE.L  highcyl,D1

  MOVEQ.L #1,D2           -> Sectors per track.
smaller_itterate:
  BTST    #0,D0
  BNE     smaller_end
  BTST    #0,D1
  BNE     smaller_end
  LSL.W   #1,D2
  LSR.L   #1,D0
  LSR.L   #1,D1
  CMP.W   #$0800,D2       -> Don't overreact (or hang).
  BLT     smaller_itterate
smaller_end:

  MOVE.L  D0,lowcyl
  MOVE.L  D1,highcyl
  MOVE.L  D2,bpt

ENDPROC lowcyl, highcyl, bpt


/****** Swap nibbles. Stupid Intel things... *********/
PROC swapL(l)
  MOVE.L  l,D0
  ROR.W   #8,D0
  SWAP.W  D0
  ROR.W   #8,D0
ENDPROC D0
