summaryrefslogtreecommitdiff
path: root/utils/rmwait/rmwait.pas
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rmwait/rmwait.pas')
-rw-r--r--utils/rmwait/rmwait.pas559
1 files changed, 559 insertions, 0 deletions
diff --git a/utils/rmwait/rmwait.pas b/utils/rmwait/rmwait.pas
new file mode 100644
index 0000000000..7877b057fb
--- /dev/null
+++ b/utils/rmwait/rmwait.pas
@@ -0,0 +1,559 @@
+{
+ rmwait - remove (delete) file(s) with optional retries
+ Copyright (C) 2009 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;
+
+
+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
+ Wait (OptWait);
+ 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 ('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; far;
+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 20091101');
+ 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: longint;
+ Par: string;
+
+begin
+{$IFDEF OS2}
+ DosCalls.DosError (0);
+{$ENDIF}
+
+ OldExit := ExitProc;
+ ExitProc := @NewExit;
+
+ 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.