PROGRAM FileAttributes;

{$G128,P128,D-} 							{010,001}

CONST

    ProgramVersion = '1.0';

(***********************************************************************

     This software has been placed into the public domain by Digital
			 Equipment Corporation.


DISCLAIMER:

The information herein is subject to change without  notice  and  should
not be construed as a commitment by Digital Equipment Corporation.

Digital Equipment Corporation assumes no responsibility for the  use  or
reliability  of  this  software.   This  software  is  provided "as is,"
without any warranty of any kind, express or implied.  Digital Equipment
Corporation  will  not	be liable in any event for any damages including
any loss of data, profit, or savings, claims against  the  user  by  any
other  party,  or  any other incidental or consequential damages arising
out of the use of, or inability to use, this software, even  if  Digital
Equipment Corporation is advised of the possibility of such damage.

DEFECT REPORTING AND SUGGESTIONS:

Please send reports of defects or suggestions for  improvement	directly
to the author:

	Brian Hetrick
	Digital Equipment Corporation
	110 Spit Brook Road  ZKO1-3/J10
	Nashua NH  03062-2698

Do NOT file a Software Performance Report on  this  software,  call  the
Telephone  Support  Center regarding this software, contact your Digital
Field Office  regarding  this  software,  or  use  any	other  mechanism
provided for Digital's supported and warranted software.


FACILITY:

    MS-DOS user utilities

ABSTRACT:

    Manipulates attributes of files

ENVIRONMENT:

    MS-DOS V2.0 or later compiled  with  Borland  International's  TURBO
    Pascal V3.0 or later

AUTHOR: Brian Hetrick, CREATION DATE: 24 November 1986.

MODIFIED BY:

	Brian Hetrick, 24-Nov-86: Version Y1.0-0
  000 - Original creation of module.
	Released to selected Easynet sites for beta test on 24	November
	1986.

	Brian Hetrick, 02-Dec-86: Version Y1.0-8
  001 - Add Gn, Pn compiler directives for compilation on IBM PC clones.
  002 - Modify IsPrefix routine to PrefixLength for  command  qualifiers
	starting with same letter.
  003 - Parse command qualifiers before and after parsing file spec,  as
	bad  command  qualifier  with no file spec prompts for file spec
	before detecting bad command qualifer.
  004 - Add /CLEAR and /REMOVE synonyms for /RESET.
  005 - Add help message if no command line given.
  006 - Allow multiple wild card specs.
  007 - Make path specifications absolute before reporting.
  008 - List file names on left side of display.
	Released to selected Easynet sites for beta test on  2	December
	1986.

	Brian Hetrick, 16-Dec-86: Version Y1.0-14
  009 - Use CtlCTrap package to trap CTRL/C, as  TURBO	Pascal's  CTRL/C
	is  not  as desired.  The undesired behavior (waiting for a Read
	or ReadLn to abort the program) appears to occur only on  MS-DOS
	or  PC-DOS  V2.x.  The PC-DOS specific TURBO Pascal had this un-
	desired behavior all along, but MS-DOS generic TURBO Pascals be-
	fore version 3.02A did not have this  behavior	on  the  Rainbow
	(due to a bug in how the TURBO CTRL/C handler was set up), where
	most testing occurred.	The undesired behavior was noticed by  a
	beta  test  site using ATTRIB Y1.0-8, the first release compiled
	with TURBO Pascal 3.02A, when it was impossible to CTRL/C out of
	listings  that were hit with the performance bug handled in edit
	011.
  010 - Construct print lines as a whole and omit trailing spaces.   Add
	the D- compiler directive and Flush (Output) to speed things up.
	This also makes all program-generated text finally go to the MS-
	-DOS standard output:  even with Pn, TURBO will use IBM  PC  ROM
	BIOS calls if the standard output is the console.
  011 - Retain attribute  from	wild  card  lookup.   This  considerably
	improves performance in highly fragmented directories.
  012 - CTRL/C entered in response to the ReadLn in CLA.PAS  does  *NOT*
	abort  the  program  at  least	under PC-DOS V2.10.  Apparently,
	using the Gn,Pn,D- set of compiler directives  entirely  defeats
	^C detection.  So use the ^C package even for command line pars-
	ing.
  013 - Detect CTRL/Z entered in response to ReadLn in CLA.PAS.
  014 - Reorder attributes in listing to put Arc and Dir, the most  pop-
	ular attributes, first.
	Released to selected Easynet sites for beta test on 17	December
	1986.

	Brian Hetrick, 30-Jan-87: Version Y1.0-16
  015 - Introduce /HELP switch to give long help message;  if no command
	line given, give short message and assume *.*.	Remove prompting
	for command parameters as there is now a 'reasonable' default.
  016 - Make help text less dense, as it is now explicitly requested and
	the user is presumably ready to deal with it.

	Brian Hetrick, 30-Jan-87: Version 1.0
  017 - Delete internal use only notice, copyright notice, etc., and set
	version  number to have no prefix or edit suffix, as will be re-
	leased to DECUS Program Library.
	Released to Easynet sites and DECUS Program Library  30  January
	1987.

***********************************************************************)
{.PA}
(*
 *  INCLUDE FILES:
 *)

{$I CtlCTrap.Pas}							{009}
{$I CLA.PAS}
{$I WildExpa.Pas}
{$I MakeAbs.Pas}							{007}
{$I BaseName.Pas}							{008}

(*
 *  LABEL DECLARATIONS:
 *)

(*
 *  CONSTANT DECLARATIONS:
 *)

CONST

    AttrMaskReadOnly	 = 1;
    AttrMaskHidden	 = 2;
    AttrMaskSystem	 = 4;
    AttrMaskSubDirectory = 16;
    AttrMaskArchive	 = 32;

(*
 *  TYPE DECLARATIONS:
 *)

TYPE

    PathSpec = WildExpandPathSpec;					{011}

(*
 *  OWN STORAGE:
 *)

VAR

    Logging	    : BOOLEAN;
    HelpDesired     : BOOLEAN;						{015}
    OptionSpecified : BOOLEAN;						{005}
    ResetMask	    : INTEGER;
    SetMask	    : INTEGER;

(*
 *  TABLE OF CONTENTS:
 *)
{.PA}
PROCEDURE PrintHelp;							{005}

(***********************************************************************{005}

FUNCTIONAL DESCRIPTION: 						{005}

    Writes a description of the program on the standard output. 	{005}

FORMAL PARAMETERS:							{005}

    None.								{005}

RETURN VALUE:								{005}

    None.								{005}

IMPLICIT INPUTS:							{005}

    None.								{005}

IMPLICIT OUTPUTS:							{005}

    None.								{005}

SIDE EFFECTS:								{005}

    None.								{005}

***********************************************************************){005}

    BEGIN								{005}

    WriteLn;										 {016}
    WriteLn ('Command line:   ATTRIB filespec [qualifier]...'); 			 {005}
    WriteLn;										 {016}
    WriteLn ('''filespec'' is a path specification possibly with wild card characters'); {005}
    WriteLn ('   in the last component');						 {005}
    WriteLn;										 {016}
    WriteLn ('''qualifier'' is one of /[NO]HELP, /[NO]LOG, /SET:value, /RESET:value,');  {005}
    WriteLn ('   /CLEAR:value, /REMOVE:value'); 					 {005}
    WriteLn;										 {016}
    WriteLn ('   /SET grants attributes');						 {016}
    WriteLn ('   /RESET, /CLEAR, and /REMOVE remove attributes');			 {016}
    WriteLn;										 {016}
    WriteLn ('''value'' is either name or (name[,name]...)');				 {005}
    WriteLn;										 {016}
    WriteLn ('''name'' is one of ARCHIVE, HIDDEN, SYSTEM, READ_ONLY');			 {005}
    WriteLn;										 {016}
    WriteLn ('All keywords may be uniquely abbreviated')				 {005}

    END;								{005}
{.PA}
FUNCTION PrefixLength							{002}
   (	Str1 : PathSpec;
	Str2 : PathSpec) : INTEGER;					{002}

(***********************************************************************

FUNCTIONAL DESCRIPTION:

    Determines whether one string is a prefix of another,  ignoring  the
    case of letters.

FORMAL PARAMETERS:

    TestString.rt.v - The string which may be a prefix of TargetString.
    TargetString.rt.v - The string of which TestString may be a prefix.

RETURN VALUE:

    Zero: TestString is not a prefix of TargetString.			{002}
    n>0:  TestString is a prefix of TargetString and has n characters.	{002}

IMPLICIT INPUTS:

    None.

IMPLICIT OUTPUTS:

    None.

SIDE EFFECTS:

    None.

***********************************************************************)

    VAR
	Chr1  : CHAR;
	Chr2  : CHAR;
	Index : INTEGER;

    BEGIN

    IF Length (Str1) <= Length (Str2)
    THEN
	BEGIN

	(*
	 *  Test string is no longer than target string, so check char-
	 *  acters
	 *)

	PrefixLength := Length (Str1);					{002}

	FOR Index := 1 TO Length (Str1)
	DO
	    BEGIN

	    Chr1 := UpCase (Str1 [Index]);
	    Chr2 := UpCase (Str2 [Index]);
	    IF Chr1 <> Chr2
	    THEN
		BEGIN							{002}

		(*
		 *  A mismatch was found, test string is not a prefix of
		 *  target string
		 *)

		PrefixLength := 0;					{002}
		Exit							{002}

		END							{002}
	    END

	END

    ELSE

	(*
	 *  Test string is longer than target string and so cannot be a
	 *  prefix
	 *)

	PrefixLength := 0						{002}

    END;
{.PA}
PROCEDURE Pad								{010,008}
   (VAR StringText : PathSpec;						{010,008}
	PadLength  : INTEGER);						{010,008}

(***********************************************************************{008}

FUNCTIONAL DESCRIPTION: 						{008}

    Adjusts a string by truncating the rightmost characters  or  padding{008}
    on the right with spaces to be a specified length.			{008}

FORMAL PARAMETERS:							{008}

    StringToPad.rt.v - The string to be adjusted.			{008}
    DesiredLength.rg.v - The length of the result string.		{008}

RETURN VALUE:								{008}

    None.								{010}

IMPLICIT INPUTS:							{008}

    None.								{008}

IMPLICIT OUTPUTS:							{008}

    None.								{008}

SIDE EFFECTS:								{008}

    None.								{008}

***********************************************************************){008}

    VAR 								{008}

	StuffIndex : INTEGER;						{008}

    BEGIN								{008}

    (*									{008}
     *	Pad on the right with blanks					{008}
     *) 								{008}

    FOR StuffIndex := Length (StringText) + 1 TO PadLength		{008}
    DO									{008}
	StringText [StuffIndex] := ' '; 				{010,008}

    (*									{008}
     *	Adjust the length						{008}
     *) 								{008}

    StringText [0] := Chr (PadLength)					{010,008}

    END;								{008}
{.PA}
PROCEDURE AppendString							{010}
   (	InsertString : PathSpec;					{010}
    VAR TargetString : PathSpec);					{010}

(***********************************************************************{010}

FUNCTIONAL DESCRIPTION: 						{010}

    Appends one string to another.					{010}

FORMAL PARAMETERS:							{010}

    StringToAdd.rt.v - The string to be appended to TargetString.	{010}
    TargetString.mt.r - The string to which StringToAdd is to be ap-	{010}
	pended. 							{010}

RETURN VALUE:								{010}

    None.								{010}

IMPLICIT INPUTS:							{010}

    None.								{010}

IMPLICIT OUTPUTS:							{010}

    None.								{010}

SIDE EFFECTS:								{010}

    None.								{010}

***********************************************************************){010}

    BEGIN								{010}

    Insert (InsertString, TargetString, Length (TargetString) + 1)	{010}

    END;								{010}
{.PA}
PROCEDURE SetKeywordBit
   (	KeywordText : PathSpec;
    VAR OptionMask  : INTEGER);

(***********************************************************************

FUNCTIONAL DESCRIPTION:

    Sets the bit in an attribute mask designated by a keyword.	The key-
    word is any of ARCHIVE, HIDDEN, READ_ONLY, or SYSTEM, or any leading
    abbreviation of one of these keywords.

FORMAL PARAMETERS:

    Keyword.rt.v - The keyword designating an attribute.
    AttributeMask.mg.r - The attribute mask in which the bit correspond-
	ing to the keyword is to be set.

RETURN VALUE:

    None.

IMPLICIT INPUTS:

    None.

IMPLICIT OUTPUTS:

    None.

SIDE EFFECTS:

    If Keyword is not an abbreviation of one of the valid  keywords,  an
    error  mesage  is  written to the standard output and the program is
    terminated.

***********************************************************************)

    BEGIN

    (*
     *	Check against list and set appropriate bit
     *)

    IF PrefixLength (KeywordText, 'HIDDEN') > 0 			{002}
    THEN

	OptionMask := OptionMask OR AttrMaskHidden

    ELSE IF PrefixLength (KeywordText, 'SYSTEM') > 0			{002}
    THEN

	OptionMask := OptionMask OR AttrMaskSystem

    ELSE IF PrefixLength (KeywordText, 'READ_ONLY') > 0 		{002}
    THEN

	OptionMask := OptionMask OR AttrMaskReadOnly

    ELSE IF PrefixLength (KeywordText, 'ARCHIVE') > 0			{002}
    THEN

	OptionMask := OptionMask OR AttrMaskArchive

    ELSE
	BEGIN

	WriteLn ('Invalid argument value: "', KeywordText, '"');
	Halt

	END

    END;
{.PA}
PROCEDURE DoSetArgument
   (	ArgumentText : PathSpec;
    VAR OptionMask   : INTEGER);

(***********************************************************************

FUNCTIONAL DESCRIPTION:

    Parses the value to the /SET or /RESET option.  The syntax of  these
    values is:

	keyword
	(keyword[,keyword]...])

    where 'keyword' is one of ARCHIVE, HIDDEN, READ_ONLY, or SYSTEM,  or
    a unique leading abbreviation of one of these.

FORMAL PARAMETERS:

    ValueText.rt.v - The text of the value to be parsed.
    AttributeMask.mg.r - The attribute	mask  in  which  the  bits  cor-
	responding to the keywords are to be set.

RETURN VALUE:

    None.

IMPLICIT INPUTS:

    None.

IMPLICIT OUTPUTS:

    None.

SIDE EFFECTS:

    May write a message to the standard output and halt program  execut-
    ion under the following circumstances:

     -	ValueText consists only of '(';
     -	ValueText starts with '(' but does not end with ')';
     -	ValueText has ')' before the end of the string;
     -	ValueText has two adjacent commas.

***********************************************************************)

    VAR

	KeyWord     : PathSpec;
	SearchIndex : INTEGER;

    BEGIN

    (*
     *	See whether single keyword or bundle
     *)

    IF ArgumentText [1] = '('
    THEN
	BEGIN

	(*
	 *  Is a bundle.  Do each word individually
	 *)

	Delete (ArgumentText, 1, 1);

	WHILE Length (ArgumentText) > 0
	DO
	    BEGIN

	    (*
	     *	Isolate the keyword
	     *)

	    SearchIndex := 1;

	    WHILE (SearchIndex <= Length (ArgumentText)) AND
		  (ArgumentText [SearchIndex] <> ',') AND
		  (ArgumentText [SearchIndex] <> ')')
	    DO
		SearchIndex := SearchIndex + 1;

	    IF SearchIndex > Length (ArgumentText)
	    THEN
		BEGIN

		WriteLn ('Invalid unterminated argument value');
		Halt

		END;

	    IF SearchIndex = 1
	    THEN
		BEGIN

		WriteLn ('Invalid null argument value');
		Halt

		END;

	    Keyword := Copy (ArgumentText, 1, SearchIndex - 1);

	    (*
	     *	Set the appropriate bit
	     *)

	    SetKeywordBit (Keyword, OptionMask);

	    (*
	     *	Ensure proper separator format
	     *)

	    IF SearchIndex = Length (ArgumentText)
	    THEN
		BEGIN

		IF ArgumentText [SearchIndex] <> ')'
		THEN
		    BEGIN

		    WriteLn ('Invalid unterminated argument value');
		    Halt

		    END

		END
	    ELSE
		BEGIN

		IF ArgumentText [SearchIndex] <> ','
		THEN
		    BEGIN

		    WriteLn ('Invalid argument value after termination');
		    Halt

		    END

		END;

	    Delete (ArgumentText, 1, SearchIndex)

	    END
	END
    ELSE

	(*
	 *  Argument value is single keyword
	 *)

	SetKeywordBit (ArgumentText, OptionMask)

    END;
{.PA}
PROCEDURE ParseCommandQualifiers;					{003}

(***********************************************************************

FUNCTIONAL DESCRIPTION:

    Parses the command line for the ATTRIB program.

    The command line has the format:

    ATTRIB wildspec [option]...

    The ATTRIB token is typed by the user but is not part of the command
    line tail retained by MS-DOS, and so does not  participate	in  this
    parse.

    Wildspec is a single path specification which may contain wild  card
    characters	in  the  last  component.  Any single token not starting
    with the option character is accepted in this position.  This is not{003}
    parsed by this routine.						{003}

    Option is one of:

	/SET:value
	/RESET:value or /CLEAR:value or /REMOVE:value			{004}
	/[NO]LOG

    where 'value' is as accepted by  the  DoSetArgument  routine  above.
    The  keywords  SET, CLEAR, REMOVE, and LOG may be abbreviated to any{004}
    unique leading substring;  the keywords RESET and REMOVE may be  ab-{004}
    breviated  all the way to R, permitted in this case as they are syn-{004}
    onyms.								{004}

    An equal sign (=) may be used in place of the colon.

FORMAL PARAMETERS:

    None.

RETURN VALUE:

    None.

IMPLICIT INPUTS:

    The command line tail at CS:0080.  [Actually, this	is  an	implicit
	input of the CLA package which this routine uses.]
    ResetMask - The mask of attribute bits to be reset.
    SetMask - The mask of attribute bits to be set.

IMPLICIT OUTPUTS:

    OptionSpecifed - The  flag	determining  whether  any  options  were
	specified on the command line.
    ResetMask - The mask of attribute bits to be reset.
    SetMask - The mask of attribute bits to be set.

SIDE EFFECTS:

    May write a message to the standard  output  and  terminate  program
    execution under the following circumstances:

     -	A null option (slash with no other characters) is present in the
	command
     -	An unrecognized option (slash followed by something other than a
	unique abbreviation of [NO]LOG, RESET, or SET) is present in the
	command
     -	A value is specified with [NO]LOG
     -	No value is specified with RESET or SET
     -	The attributes specified with /SET and /RESET are not disjoint.

***********************************************************************)

    VAR

	DummyArg    : PathSpec;
	Keyword     : PathSpec;
	SearchIndex : INTEGER;
	TargetMask  : ^ INTEGER;
	ValueType   : INTEGER;

    BEGIN

    (*
     *	Get all switches						{003}
     *)

    DummyArg := CommandLineArgument ('', '', TRUE);

    WHILE Length (DummyArg) > 0
    DO
	BEGIN

	(*
	 *  Delete the leading slash
	 *)

	Delete (DummyArg, 1, 1);
	IF Length (DummyArg) = 0
	THEN
	    BEGIN

	    WriteLn ('Invalid null option');
	    Halt

	    END;

	(*
	 *  Extract the keyword
	 *)

	SearchIndex := 1;
	WHILE (SearchIndex <= Length (DummyArg)) AND
	      (DummyArg [SearchIndex] <> ':') AND
	      (DummyArg [SearchIndex] <> '=')
	DO
	    SearchIndex := SearchIndex + 1;

	Keyword := Copy (DummyArg, 1, SearchIndex - 1);

	(*
	 *  Delete all but the keyword value
	 *)

	IF SearchIndex > Length (DummyArg)
	THEN
	    SearchIndex := Length (DummyArg);
	Delete (DummyArg, 1, SearchIndex);

	(*
	 *  Try to match the keyword to a possible keyword
	 *)

	IF PrefixLength (Keyword, 'LOG') > 0				{002}
	THEN
	    BEGIN

	    Logging := TRUE;
	    ValueType := 0

	    END
	ELSE IF PrefixLength (Keyword, 'NOLOG') > 2			{002}
	THEN
	    BEGIN

	    Logging := FALSE;
	    ValueType := 0

	    END
	ELSE IF PrefixLength (Keyword, 'SET') > 0			{002}
	THEN
	    BEGIN

	    TargetMask := Addr (SetMask);
	    ValueType  := 1

	    END
	ELSE IF (PrefixLength (Keyword, 'RESET')  > 0) OR		{004,002}
		(PrefixLength (Keyword, 'CLEAR')  > 0) OR		{004}
		(PrefixLength (Keyword, 'REMOVE') > 0)			{004}
	THEN
	    BEGIN

	    TargetMask := Addr (ResetMask);
	    ValueType  := 1

	    END
	ELSE IF PrefixLength (Keyword, 'HELP') > 0			{015}
	THEN								{015}
	    BEGIN							{015}

	    HelpDesired := TRUE;					{015}
	    ValueType := 0						{015}

	    END 							{015}
	ELSE IF PrefixLength (Keyword, 'NOHELP') > 2			{015}
	THEN								{015}
	    BEGIN							{015}

	    HelpDesired := FALSE;					{015}
	    ValueType := 0						{015}

	    END 							{015}
	ELSE
	    BEGIN

	    WriteLn ('Invalid switch: "', Keyword, '"');
	    Halt

	    END;

	(*
	 *  Parse the switch value
	 *)

	IF ValueType = 0
	THEN
	    BEGIN

	    IF Length (DummyArg) > 0
	    THEN
		BEGIN

		WriteLn ('/LOG switch does not take value');
		Halt

		END
	    END
	ELSE
	    BEGIN

	    IF Length (DummyArg) = 0
	    THEN
		BEGIN

		WriteLn ('/SET and /RESET require value');
		Halt

		END;

	    DoSetArgument (DummyArg, TargetMask ^)

	    END;

	(*								{005}
	 *  Note option parsed						{005}
	 *)								{005}

	OptionSpecified := TRUE;					{005}

	(*
	 *  Get next switch
	 *)

	DummyArg := CommandLineArgument ('', '', TRUE)

	END;

    (*
     *	Check for non-interference of set and reset masks
     *)

    IF (SetMask AND ResetMask) <> 0
    THEN
	BEGIN

	WriteLn ('Same attribute specified for both /SET and /RESET');
	Halt

	END

    END;
{.PA}
(***********************************************************************

FUNCTIONAL DESCRIPTION:

    Manipulates attributes of files.

FORMAL PARAMETERS:

    None.

RETURN VALUE:

    None.

IMPLICIT INPUTS:

    Logging - The flag showing whether	attribute  messages  are  to  be
	written to the standard output.
    ResetMask - The mask of attribute bits to be reset in the files sel-
	ected by WildSpec.
    SetMask - The mask of attribute bits to be set in the files  select-
	ed by WildSpec.

IMPLICIT OUTPUTS:

    None.

SIDE EFFECTS:

    May write messages to the standard output.
    May modify attributes of files.

***********************************************************************)

TYPE

    RegPack = RECORD
	CASE INTEGER OF
	 0: (AX, BX, CX, DX, DP, SI, DI, DS, ES, Flags : INTEGER);
	 1: (AL, AH, BL, BH, CL, CH, DL, DH	       : BYTE)
	END;

VAR

    FileSpec  : PathSpec;
    NewAttr   : INTEGER;
    OldAttr   : INTEGER;
    OutLine   : PathSpec;						{010}
    Registers : RegPack;
    WildSpec  : PathSpec;						{003}

BEGIN

(*									{012}
 *  Set up Control/C trapping for program				{012}
 *)									{012}

CtrlCSetup;								{012,009}

(*
 *  Print banner
 *)

WriteLn ('ATTRIB version ', ProgramVersion);
Flush (Output); 							{012}

(*
 *  Parse the command line
 *)

Logging 	:= TRUE;						{003}
OptionSpecified := FALSE;						{005}
HelpDesired	:= FALSE;						{015}
ResetMask	:= 0;							{003}
SetMask 	:= 0;							{003}

ParseCommandQualifiers; 						{003}

WildSpec := CommandLineArgument ('', '', FALSE);			{005}

IF (Length (WildSpec) = 0) AND NOT OptionSpecified			{005}
THEN									{005}
    BEGIN								{005}

    (*									{015}
     *	A totally blank command line.  Assume *.* and  give  short  mes-{015}
     *	sage.								{015}
     *) 								{015}

    WildSpec := '*.*';							{015}
    WriteLn;								{015}
    WriteLn ('Use /HELP qualifier for help')				{015}

    END;								{005}

(*									{015}
 *  If help requested, give it						{015}
 *)									{015}

IF HelpDesired								{015}
THEN									{015}

    PrintHelp;								{015}

(*									{006}
 *  Scan with each wild card specification				{006}
 *)									{006}

WHILE (NOT CtrlCOccurred) AND (Length (WildSpec) > 0)			{009,006}
DO									{006}
    BEGIN								{006}

    (*									{007}
     *	Append *.* if path ends in : or \				{007}
     *) 								{007}

    IF WildSpec [Length (WildSpec)] IN [':', '/', '\']			{007}
    THEN								{007}

	Insert ('*.*', WildSpec, Length (WildSpec) + 1);		{007}

    (*									{007}
     *	Make the wild card path absolute				{007}
     *) 								{007}

    MakePathAbsolute (WildSpec);					{007}

    (*									{008}
     *	Log the specification						{008}
     *) 								{008}

    IF Logging								{008}
    THEN								{008}
	BEGIN								{008}

	WriteLn;							{008}
	WriteLn (WildSpec, ':') 					{008}

	END;								{008}

    (*
     *	Initialize the wild card scan
     *)

    IF NOT WildExpandInitialize (WildSpec, $17)
    THEN
	BEGIN

	IF Logging							{008}
	THEN								{008}

	    WriteLn ('  No files found')				{008}

	ELSE								{008}

	    WriteLn ('No files found for ', WildSpec)			{008}

	END								{008}
    ELSE								{006}
	BEGIN								{006}

	WildExpandContinue (FileSpec, OldAttr); 			{011}

	WHILE (NOT CtrlCOccurred) AND (Length (FileSpec) > 0)		{009}
	DO
	    BEGIN

	    (*
	     *	Append NUL for MS-DOS
	     *)

	    FileSpec [Length (FileSpec) + 1] := #$00;

	    (*
	     *	Obtain changed attributes
	     *)

	    NewAttr := (OldAttr OR SetMask) AND NOT ResetMask;

	    (*
	     *	Modify attributes
	     *)

	    IF NewAttr <> OldAttr
	    THEN
		BEGIN

		Registers . AH := $43;
		Registers . AL := $01;
		Registers . CX := NewAttr AND NOT AttrMaskSubDirectory;
		Registers . DS := Seg (FileSpec [1]);
		Registers . DX := Ofs (FileSpec [1]);
		MsDos (Registers)

		END
	    ELSE							{010}

		Registers . Flags := 0; 				{010}

	    IF (Registers . Flags AND 1) <> 0
	    THEN

		WriteLn ('Cannot change attributes for ', FileSpec)	{008}

	    ELSE
		BEGIN

		(*
		 *  List new attributes and file name
		 *)

		IF Logging
		THEN
		    BEGIN

		    OutLine := '  ';					{010}
		    AppendString (BaseName (FileSpec), OutLine);	{010}

		    IF (NewAttr AND AttrMaskArchive) <> 0
		    THEN
			BEGIN						{010}
			Pad (OutLine, 16);				{014,010}
			AppendString ('Arc', OutLine)			{010}
			END;						{010}

		    IF (NewAttr AND AttrMaskSubDirectory) <> 0
		    THEN
			BEGIN						{010}
			Pad (OutLine, 20);				{014,010}
			AppendString ('Dir', OutLine)			{010}
			END;						{010}

		    IF (NewAttr AND AttrMaskReadOnly) <> 0
		    THEN
			BEGIN						{010}
			Pad (OutLine, 24);				{014,010}
			AppendString ('R/O', OutLine)			{010}
			END;						{010}

		    IF (NewAttr AND AttrMaskHidden) <> 0
		    THEN
			BEGIN						{010}
			Pad (OutLine, 28);				{014,010}
			AppendString ('Hid', OutLine)			{010}
			END;						{010}

		    IF (NewAttr AND AttrMaskSystem) <> 0
		    THEN
			BEGIN						{010}
			Pad (OutLine, 32);				{014,010}
			AppendString ('Sys', OutLine)			{010}
			END;						{010}

		    WriteLn (OutLine);					{010,008}
		    Flush (Output)					{009}

		    END
		END;

	    WildExpandContinue (FileSpec, OldAttr)			{011}

	    END 							{006}
	END;

    WildSpec := CommandLineArgument ('', '', FALSE)			{006}

    END 								{006}
END.
