123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- {
- rmwait - remove (delete) file(s) with optional retries
- Copyright (C) 2009-2011 by Tomas Hajny, member of the Free Pascal team
- This tool tries to mimic behaviour of GNU rm, but it provides
- the additional feature of retries and it also fixes some issues
- appearing at least with the Win32 port of version 3.13.
- See the file COPYING, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- }
- program rmwait;
- {$D
- Remove (delete) file(s) with optional retries.
- }
- { $DEFINE DONOTHING}
- uses
- {$IFDEF GO32V2}
- Go32,
- {$ENDIF GO32V2}
- {$IFDEF OS2}
- DosCalls,
- {$ENDIF OS2}
- {$IFDEF WINDOWS}
- Windows,
- {$ENDIF WINDOWS}
- {$IFDEF UNIX}
- BaseUnix,
- {$ENDIF UNIX}
- Dos;
- const
- OptDirectories: boolean = false;
- OptForce: boolean = false;
- OptInteractive: boolean = false;
- OptRecursive: boolean = false;
- OptVerbose: boolean = false;
- OptRetries: longint = 1;
- OptWait: longint = 5;
- OptsStop: boolean = false;
- RmWaitEnvVarName = 'RMWAIT_OPTS';
- var
- OldExit: pointer;
- Deleted: cardinal;
- procedure VerbLine (S: string); inline;
- begin
- if OptVerbose then
- WriteLn (S);
- end;
- procedure ForceErrorLn (S: string); inline;
- begin
- WriteLn (ParamStr (0), ': ', S);
- end;
- procedure ErrorLn (S: string); inline;
- begin
- { if not (OptForce) then}
- ForceErrorLn (S);
- end;
- procedure GenericErrorLn (S: string; N: longint); inline;
- begin
- if not (OptForce) then
- WriteLn (ParamStr (0), ': ', S, ' (', N, ')');
- end;
- procedure ClearIO; inline;
- begin
- if IOResult <> 0 then ;
- end;
- procedure Wait (Seconds: Cardinal);
- {$IFDEF GO32v2}
- var
- R: Registers;
- T0, T1, T2: int64;
- DayOver: boolean;
- begin
- (* Sleep is supposed to give up time slice - DOS Idle Interrupt chosen
- because it should be supported in all DOS versions. *)
- R.AH := $2C;
- RealIntr($21, R);
- T0 := R.CH * 3600 + R.CL * 60 + R.DH;
- T2 := T0 + Seconds;
- DayOver := T2 > (24 * 3600);
- repeat
- Intr ($28, R);
- (* R.AH := $2C; - should be preserved. *)
- RealIntr($21, R);
- T1 := R.CH * 3600 + R.CL * 60 + R.DH;
- if DayOver and (T1 < T0) then
- Inc (T1, 24 * 3600);
- until T1 >= T2;
- end;
- {$ELSE GO32v2}
- {$IFDEF OS2}
- begin
- DosSleep (Seconds * 1000);
- end;
- {$ELSE OS2}
- {$IFDEF UNIX}
- begin
- fpSleep (Seconds * 1000);
- end;
- {$ELSE UNIX}
- {$IFDEF WINDOWS}
- begin
- Sleep (Seconds * 1000);
- end;
- {$ELSE WINDOWS}
- var
- T0, T1, T2: int64;
- begin
- {$WARNING No sleeping is performed with this platform!}
- T0 := GetMSCount;
- T2 := T0 + Seconds * 1000;
- repeat
- T1 := GetMSCount;
- (* GetMSCount returning lower value than in the first check indicates overflow
- and is treated as end of the waiting period due to undefined range. *)
- until (T1 >= T2) or (T1 < T0);
- end;
- {$ENDIF WINDOWS}
- {$ENDIF UNIX}
- {$ENDIF OS2}
- {$ENDIF GO32v2}
- procedure ClearAttribs (var F: file); inline;
- var
- W: word;
- begin
- {$I-}
- GetFAttr (F, W);
- if W and (ReadOnly or SysFile) <> 0 then
- SetFAttr (F, W and not ReadOnly and not SysFile);
- ClearIO;
- {$I+}
- end;
- function StrF (U: cardinal): string; inline;
- begin
- Str (U, StrF);
- end;
- function CheckOK (Msg: string; FN: PathStr): boolean;
- var
- Resp: string;
- begin
- Write (ParamStr (0), ': ', Msg, '''', FN, '''? ');
- ReadLn (Resp);
- CheckOK := (Length (Resp) > 0) and (UpCase (Resp [1]) = 'Y');
- end;
- procedure DelFile (FN: PathStr); inline;
- var
- F: file;
- R, Tries: longint;
- begin
- VerbLine ('removing ''' + FN + '''');
- Inc (Deleted);
- if not (OptInteractive) or CheckOK ('remove ', FN) then
- begin
- Assign (F, FN);
- if OptForce then
- ClearAttribs (F);
- Tries := 1;
- repeat
- {$I-}
- {$IFDEF DONOTHING}
- WriteLn ('Debug: ', FN);
- {$ELSE DONOTHING}
- Erase (F);
- {$ENDIF DONOTHING}
- R := IOResult;
- {$I+}
- Inc (Tries);
- if (R = 5) and (Tries <= OptRetries) then
- begin
- VerbLine ('Removal attempt failed, waiting ' + StrF (OptWait) + ' seconds before trying again...');
- Wait (OptWait);
- end;
- until (R <> 5) or (Tries > OptRetries);
- case R of
- 0: ;
- 2: ErrorLn (FN + ': No such file or directory');
- 5: ErrorLn (FN + ': Permission denied');
- else
- GenericErrorLn (FN + ': Cannot be removed', R);
- end;
- end;
- end;
- procedure DelDir (FN: PathStr); inline;
- var
- F: file;
- R, Tries: longint;
- begin
- VerbLine ('removing ''' + FN + '''');
- Inc (Deleted);
- if not (OptInteractive) or CheckOK ('remove directory ', FN) then
- begin
- if OptForce then
- begin
- Assign (F, FN);
- ClearAttribs (F);
- end;
- Tries := 1;
- repeat
- {$I-}
- {$IFDEF DONOTHING}
- WriteLn ('Debug: Directory ', FN);
- {$ELSE DONOTHING}
- RmDir (FN);
- {$ENDIF DONOTHING}
- R := IOResult;
- {$I+}
- Inc (Tries);
- if (R = 5) and (Tries <= OptRetries) then
- begin
- VerbLine ('Removal attempt failed, waiting ' + StrF (OptWait) + ' seconds before trying again...');
- Wait (OptWait);
- end;
- until (R <> 5) or (Tries > OptRetries);
- case R of
- 0: ;
- 5: ErrorLn (FN + ': Permission denied');
- else
- GenericErrorLn (FN + ': Cannot be removed', R);
- end;
- end;
- end;
- procedure Syntax;
- begin
- WriteLn;
- WriteLn ('RmWait - remove (delete) file(s) with optional retries');
- WriteLn;
- WriteLn ('Syntax:');
- WriteLn (ParamStr (0) + ' [<options>...] [<file specifications>...]');
- WriteLn;
- WriteLn ('<file specifications> may contain wildcards ''*'' and ''?''.');
- WriteLn;
- WriteLn ('Options:');
- WriteLn (' -d, --directory remove directory. even if non-empty');
- WriteLn (' -f, --force ignore non-existent files, never prompt');
- WriteLn (' -i, --interactive prompt before any removal');
- WriteLn (' -r, -R, --recursive remove the contents of directories recursively');
- WriteLn (' -v, --verbose explain what is being done');
- WriteLn (' --version output version information and exit');
- WriteLn (' -h, -?, --help display this help and exit');
- WriteLn (' -t[<N>[,<T>]], --try[<N>[,<T>]] in case of errors, retry deleting N times');
- WriteLn (' (default 3 times) waiting T seconds between');
- WriteLn (' individual attempts (default 5 seconds)');
- WriteLn (' -- stop processing of options');
- WriteLn;
- WriteLn ('Options may also be passed via environment variable RMWAIT_OPTS.');
- WriteLn;
- WriteLn ('To remove a file whose name starts with a ''-'', for example ''-file'',');
- WriteLn ('use one of these commands:');
- WriteLn (' rm -- -file');
- WriteLn (' rm ./-file');
- WriteLn;
- Halt;
- end;
- procedure ParError (S: string); inline;
- begin
- ForceErrorLn (S);
- WriteLn;
- Syntax;
- end;
- procedure ProcessFSpec (FN: PathStr);
- var
- SR: SearchRec;
- D, BaseDir: DirStr;
- N, BaseName: NameStr;
- E: ExtStr;
- RemFNDir: boolean;
- begin
- RemFNDir := false;
- {$IF NOT DEFINED (OS2) and NOT DEFINED (WINDOWS) and NOT DEFINED (DPMI) and NOT DEFINED (UNIX) and NOT DEFINED (MACOS) and NOT DEFINED (AMIGA) and NOT DEFINED (NETWARE)}
- {$WARNING Proper behaviour for this target platform has not been checked!}
- {$ENDIF}
- {$IF NOT DEFINED (MACOS) and NOT DEFINED (AMIGA)}
- (* Special case - root directory needs to be treated in a special way. *)
- {$IFDEF UNIX}
- if (Length (FN) = 1)
- {$ELSE UNIX}
- {$IF DEFINED (OS2) or DEFINED (WINDOWS) or DEFINED (DPMI)}
- if (((Length (FN) = 3) and (FN [2] = DriveSeparator))
- or ((Length (FN) = 2) and (FN [1] = DirectorySeparator)))
- (* Root of UNC path - nonsense, but changing it to root of current drive would be dangerous. *)
- {$ELSE}
- {$IFDEF NETWARE}
- if (Length (FN) = Pos (DirectorySeparator, FN))
- {$ENDIF NETWARE}
- {$ENDIF}
- and (FN [Length (FN)] = DirectorySeparator) then
- {$ENDIF UNIX}
- if OptRecursive then
- begin
- BaseDir := FN;
- BaseName := AllFilesMask;
- end
- else
- begin
- ErrorLn (FN + ': is a directory');
- Exit;
- end
- else
- {$ENDIF}
- begin
- (* Check if the specification directly corresponds to a directory *)
- if FN [Length (FN)] = DirectorySeparator then
- Delete (FN, Length (FN), 1);
- FSplit (FN, D, N, E);
- FindFirst (FN, (AnyFile or Directory) and not VolumeID, SR);
- if (DosError = 0) and (SR.Attr and Directory = Directory) and
- ((SR.Name = N + E) or
- (* Checking equal names is not sufficient with case preserving file systems. *)
- (Pos ('?', FN) = 0) and (Pos ('*', FN) = 0)) then
- if OptRecursive then
- begin
- BaseDir := FN;
- if BaseDir [Length (BaseDir)] <> DirectorySeparator then
- BaseDir := BaseDir + DirectorySeparator;
- BaseName := AllFilesMask;
- RemFNDir := true;
- end
- else
- if OptDirectories then
- RemFNDir := true
- else
- begin
- ErrorLn (FN + ': is a directory');
- Exit;
- end
- else
- begin
- BaseDir := D;
- BaseName := N + E;
- end;
- FindClose (SR);
- end;
- FindFirst (BaseDir + BaseName, AnyFile and not Directory and not VolumeID, SR);
- while DosError = 0 do
- begin
- DelFile (BaseDir + SR.Name);
- FindNext (SR);
- end;
- FindClose (SR);
- if OptRecursive then
- begin
- FindFirst (BaseDir + BaseName, (AnyFile or Directory) and not VolumeID, SR);
- while DosError = 0 do
- begin
- if (SR.Attr and Directory > 0) and
- ((Length (SR.Name) <> 1) or (SR.Name [1] <> '.')) and
- ((Length (SR.Name) <> 2) or (SR.Name [1] <> '.') or (SR.Name [2] <> '.')) and
- (not (OptInteractive) or CheckOK ('descend directory ', BaseDir + SR.Name)) then
- ProcessFSpec (BaseDir + SR.Name);
- FindNext (SR);
- end;
- FindClose (SR);
- end;
- if RemFNDir then
- DelDir (FN);
- end;
- procedure NewExit; {$IFNDEF FPC} far;{$ENDIF FPC}
- begin
- ExitProc := OldExit;
- if (ErrorAddr <> nil) or (ExitCode <> 0) then
- begin
- ErrorAddr := nil;
- case ExitCode of
- 202: WriteLn ('Directory tree too deep!!');
- 4: WriteLn ('Increase the FILES directive in CONFIG.SYS!!');
- 5, 101, 150..152, 154, 156..158, 160..162: WriteLn ('I/O error (',
- ExitCode, ')!!');
- else
- WriteLn ('Internal error (', ExitCode, ')!!');
- end;
- WriteLn;
- end;
- end;
- procedure AllowSlash (var S: string); inline;
- var
- I: byte;
- begin
- if DirectorySeparator <> '/' then
- for I := 1 to Length (S) do
- begin
- if S [I] = '/' then
- S [I] := DirectorySeparator;
- end;
- end;
- procedure ProcessOpts (S: string);
- var
- I: longint;
- procedure ParseOptTries; inline;
- var
- SN: string;
- J, N, Err: longint;
- begin
- J := Succ (I);
- while (J <= Length (S)) and (S [J] in ['0'..'9']) do
- Inc (J);
- if J = Succ (I) then
- OptRetries := 3
- else
- begin
- SN := Copy (S, Succ (I), J - I - 1);
- Val (SN, N, Err);
- if Err <> 0 then
- ParError ('invalid value for retry attempts ''' + SN + '''');
- OptRetries := N;
- I := Pred (J);
- if (J < Length (S)) and (S [J] = ',') then
- begin
- Inc (J);
- Inc (I);
- while (J <= Length (S)) and (S [J] in ['0'..'9']) do
- Inc (J);
- if J > Succ (I) then
- begin
- SN := Copy (S, Succ (I), J - I - 1);
- Val (SN, N, Err);
- if Err <> 0 then
- ParError ('invalid value for retry wait time ''' + SN + '''');
- OptWait := N;
- I := Pred (J);
- end;
- end;
- end;
- end;
- begin
- if S [2] = '-' then
- if Length (S) = 2 then
- OptsStop := true
- else
- begin
- Delete (S, 1, 2);
- for I := 1 to Length (S) do
- S [I] := Upcase (S [I]);
- if S = 'HELP' then Syntax;
- if S = 'DIRECTORY' then
- OptDirectories := true
- else if S = 'FORCE' then
- OptForce := true
- else if S = 'INTERACTIVE' then
- OptInteractive := true
- else if S = 'RECURSIVE' then
- OptRecursive := true
- else if S = 'VERBOSE' then
- OptVerbose := true
- else if S = 'VERSION' then
- begin
- WriteLn ('rmwait - version 20110104');
- Halt;
- end
- else if Copy (S, 1, 3) = 'TRY' then
- begin
- I := 3;
- ParseOptTries;
- if I <> Length (S) then
- ParError ('unrecognized option ''' + S + '''');
- end
- else
- ParError ('unrecognized option ''' + S + '''');
- end
- else
- begin
- I := 2;
- repeat
- case Upcase (S [I]) of
- 'H', '?': Syntax;
- 'D': OptDirectories := true;
- 'F': OptForce := true;
- 'I': OptInteractive := true;
- 'R': OptRecursive := true;
- 'V': OptVerbose := true;
- 'T': ParseOptTries;
- else
- ParError ('invalid option -- ' + S [I])
- end;
- Inc (I);
- until (I > Length (S));
- end;
- end;
- var
- J, K, L: longint;
- EnvOpts, Par: string;
- begin
- {$IFDEF OS2}
- DosCalls.DosError (0);
- {$ENDIF}
- OldExit := ExitProc;
- ExitProc := @NewExit;
- EnvOpts := GetEnv (RmWaitEnvVarName);
- K := 1;
- while (K < Length (EnvOpts)) and not OptsStop do
- begin
- while (EnvOpts [K] = ' ') and (K < Length (EnvOpts)) do
- Inc (K);
- if EnvOpts [K] = '-' then
- begin
- L := Succ (K);
- while ((L <= Length (EnvOpts)) and (EnvOpts [L] <> ' ')) do
- Inc (L);
- Par := Copy (EnvOpts, K, L - K);
- ProcessOpts (Par);
- K := Succ (L);
- end
- else
- Syntax;
- if OptsStop then
- begin
- EnvOpts := '';
- OptsStop := false;
- end;
- end;
- J := ParamCount;
- if J = 0 then
- Syntax
- else
- begin
- K := 1;
- Par := ParamStr (K);
- while (K <= J) and (Par [1] = '-') and (Length (Par) > 1) and not OptsStop do
- begin
- ProcessOpts (Par);
- Inc (K);
- Par := ParamStr (K);
- end;
- if K > J then
- Syntax
- else
- repeat
- AllowSlash (Par);
- Deleted := 0;
- ProcessFSpec (FExpand (Par));
- if Deleted = 0 then
- ErrorLn (ParamStr (K) + ': No such file or directory');
- Inc (K);
- Par := ParamStr (K);
- until K > J;
- end;
- end.
|