doublecmd/components/doublecmd/dcunix.pas
2026-06-13 23:53:32 +03:00

649 lines
18 KiB
ObjectPascal

{
Double commander
-------------------------------------------------------------------------
This unit contains Unix specific functions
Copyright (C) 2015-2024 Alexander Koblov (alexx2000@mail.ru)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Notes:
1. TDarwinStat64 is the workaround for the bug of BaseUnix.Stat in FPC.
on MacOS with x86_64, Stat64 should be used instead of Stat.
and lstat64() should be called instead of lstat().
}
unit DCUnix;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
{$packrecords c}
interface
uses
InitC, BaseUnix, UnixType, DCBasicTypes, SysUtils;
const
{$IF DEFINED(LINUX)}
FD_CLOEXEC = 1;
O_CLOEXEC = &02000000;
O_PATH = &010000000;
_SC_NPROCESSORS_CONF = 83;
_SC_NPROCESSORS_ONLN = 84;
{$ELSEIF DEFINED(FREEBSD)}
O_CLOEXEC = &04000000;
_SC_NPROCESSORS_CONF = 57;
_SC_NPROCESSORS_ONLN = 58;
CLOSE_RANGE_CLOEXEC = (1 << 2);
{$ELSEIF DEFINED(NETBSD)}
O_CLOEXEC = $00400000;
{$ELSEIF DEFINED(HAIKU)}
FD_CLOEXEC = 1;
O_CLOEXEC = $00000040;
{$ELSEIF DEFINED(DARWIN)}
F_NOCACHE = 48;
O_CLOEXEC = $1000000;
_SC_NPROCESSORS_CONF = 57;
_SC_NPROCESSORS_ONLN = 58;
{$ELSE}
O_CLOEXEC = 0;
{$ENDIF}
{$IF DEFINED(LINUX)}
{$I dclinuxmagic.inc}
{$ENDIF}
type
{$IF DEFINED(LINUX)}
TUnixTime =
{$IF DEFINED(CPUAARCH64)}
Int64
{$ELSEIF DEFINED(CPUMIPS)}
LongInt
{$ELSE}UIntPtr{$ENDIF};
TUnixMode =
{$IF DEFINED(CPUPOWERPC)}
LongInt
{$ELSE}Cardinal{$ENDIF};
{$ELSE}
TUnixTime = TTime;
TUnixMode = TMode;
{$ENDIF}
type
PTimeStruct = ^TTimeStruct;
TTimeStruct = record
tm_sec: cint; //* Seconds. [0-60] (1 leap second)
tm_min: cint; //* Minutes. [0-59]
tm_hour: cint; //* Hours. [0-23]
tm_mday: cint; //* Day. [1-31]
tm_mon: cint; //* Month. [0-11]
tm_year: cint; //* Year - 1900.
tm_wday: cint; //* Day of week. [0-6]
tm_yday: cint; //* Days in year. [0-365]
tm_isdst: cint; //* DST. [-1/0/1]
tm_gmtoff: clong; //* Seconds east of UTC.
tm_zone: pansichar; //* Timezone abbreviation.
end;
type
//en Password file entry record
passwd = record
pw_name: PChar; //en< user name
pw_passwd: PChar; //en< user password
pw_uid: uid_t; //en< user ID
pw_gid: gid_t; //en< group ID
{$IF DEFINED(BSD)}
pw_change: time_t; //en< password change time
pw_class: PChar; //en< user access class
{$ENDIF}
{$IF NOT DEFINED(HAIKU)}
pw_gecos: PChar; //en< real name
{$ENDIF}
pw_dir: PChar; //en< home directory
pw_shell: PChar; //en< shell program
{$IF DEFINED(HAIKU)}
pw_gecos: PChar; //en< real name
{$ENDIF}
{$IF DEFINED(BSD)}
pw_expire: time_t; //en< account expiration
pw_fields: cint; //en< internal: fields filled in
{$ENDIF}
end;
TPasswordRecord = passwd;
PPasswordRecord = ^TPasswordRecord;
//en Group file entry record
group = record
gr_name: PChar; //en< group name
gr_passwd: PChar; //en< group password
gr_gid: gid_t; //en< group ID
gr_mem: ^PChar; //en< group members
end;
TGroupRecord = group;
PGroupRecord = ^TGroupRecord;
type
{$IF DEFINED(DARWIN)}
TDarwinStat64 = record { the types are real}
st_dev : dev_t; // inode's device
st_mode : mode_t; // inode protection mode
st_nlink : nlink_t; // number of hard links
st_ino : cuint64; // inode's number
st_uid : uid_t; // user ID of the file's owner
st_gid : gid_t; // group ID of the file's group
st_rdev : dev_t; // device type
st_atime : time_t; // time of last access
st_atimensec : clong; // nsec of last access
st_mtime : time_t; // time of last data modification
st_mtimensec : clong; // nsec of last data modification
st_ctime : time_t; // time of last file status change
st_ctimensec : clong; // nsec of last file status change
st_birthtime : time_t; // File creation time
st_birthtimensec : clong; // nsec of file creation time
st_size : off_t; // file size, in bytes
st_blocks : cint64; // blocks allocated for file
st_blksize : cuint32; // optimal blocksize for I/O
st_flags : cuint32; // user defined flags for file
st_gen : cuint32; // file generation number
st_lspare : cint32;
st_qspare : array[0..1] Of cint64;
end;
TDCStat = TDarwinStat64;
{$ELSE}
TDCStat = BaseUnix.Stat;
{$ENDIF}
PDCStat = ^TDCStat;
TDCStatHelper = record Helper for TDCStat
Public
function birthtime: TFileTimeEx; inline;
function mtime: TFileTimeEx; inline;
function atime: TFileTimeEx; inline;
function ctime: TFileTimeEx; inline;
end;
Function DC_fpLstat( const path:RawByteString; var Info:TDCStat ): cint; inline;
// nanoseconds supported
function DC_FileSetTime(const FileName: String;
const mtime : TFileTimeEx;
const birthtime: TFileTimeEx;
const atime : TFileTimeEx ): Boolean;
// nanoseconds supported, does not follow symbolic links
function DC_SymLinkSetTime(const FileName: String;
const mtime : TFileTimeEx;
const atime : TFileTimeEx ): Boolean;
{en
Set the close-on-exec flag to all
}
procedure FileCloseOnExecAll;
{en
Set the close-on-exec (FD_CLOEXEC) flag
}
procedure FileCloseOnExec(Handle: System.THandle); inline;
{en
Find mount point of file system where file is located
@param(FileName File name)
@returns(Mount point of file system)
}
function FindMountPointPath(const FileName: String): String;
{en
Change owner and group of a file (does not follow symbolic links)
@param(path Full path to file)
@param(owner User ID)
@param(group Group ID)
@returns(On success, zero is returned. On error, -1 is returned, and errno is set appropriately)
}
function fpLChown(path : String; owner : TUid; group : TGid): cInt;
{en
Set process group ID for job control
}
function setpgid(pid, pgid: pid_t): cint; cdecl; external clib;
{en
The getenv() function searches the environment list to find the
environment variable name, and returns a pointer to the corresponding
value string.
}
function getenv(name: PAnsiChar): PAnsiChar; cdecl; external clib;
{en
Change or add an environment variable
@param(name Environment variable name)
@param(value Environment variable value)
@param(overwrite Overwrite environment variable if exist)
@returns(The function returns zero on success, or -1 if there was
insufficient space in the environment)
}
function setenv(const name, value: PAnsiChar; overwrite: cint): cint; cdecl; external clib;
{en
Remove an environment variable
@param(name Environment variable name)
@returns(The function returns zero on success, or -1 on error)
}
function unsetenv(const name: PAnsiChar): cint; cdecl; external clib;
{en
Get password file entry
@param(uid User ID)
@returns(The function returns a pointer to a structure containing the broken-out
fields of the record in the password database that matches the user ID)
}
function getpwuid(uid: uid_t): PPasswordRecord; cdecl; external clib;
{en
Get password file entry
@param(name User name)
@returns(The function returns a pointer to a structure containing the broken-out
fields of the record in the password database that matches the user name)
}
function getpwnam(const name: PChar): PPasswordRecord; cdecl; external clib;
{en
Get group file entry
@param(gid Group ID)
@returns(The function returns a pointer to a structure containing the broken-out
fields of the record in the group database that matches the group ID)
}
function getgrgid(gid: gid_t): PGroupRecord; cdecl; external clib;
{en
Get group file entry
@param(name Group name)
@returns(The function returns a pointer to a structure containing the broken-out
fields of the record in the group database that matches the group name)
}
function getgrnam(name: PChar): PGroupRecord; cdecl; external clib;
{en
Get configuration information at run time
}
function sysconf(name: cint): clong; cdecl; external clib;
function FileLock(Handle: System.THandle; Mode: cInt): System.THandle;
function fpMkTime(tm: PTimeStruct): TTime;
function fpLocalTime(timer: PTime; tp: PTimeStruct): PTimeStruct;
{$IF DEFINED(LINUX)}
var
KernVersion: UInt16;
function fpFDataSync(fd: cint): cint;
function fpCloneFile(src_fd, dst_fd: cint): Boolean;
function fpFAllocate(fd: cint; mode: cint; offset, len: coff_t): cint;
{$ENDIF}
{$IF DEFINED(UNIX) AND NOT DEFINED(DARWIN)}
function fnmatch(const pattern: PAnsiChar; const str: PAnsiChar; flags: cint): cint; cdecl; external clib;
{$ENDIF}
implementation
uses
Unix, DCConvertEncoding, DCOSUtils, LazUTF8
{$IF DEFINED(DARWIN)}
, DCDarwin
{$ELSEIF DEFINED(LINUX)}
, Dos, DCLinux
{$ENDIF}
;
{$IF not DEFINED(LINUX)}
function TDCStatHelper.birthtime: TFileTimeEx;
begin
{$IF DEFINED(HAIKU)}
Result.sec:= st_crtime;
Result.nanosec:= st_crtimensec;
{$ELSE}
Result.sec:= st_birthtime;
Result.nanosec:= st_birthtimensec;
{$ENDIF}
end;
function TDCStatHelper.mtime: TFileTimeEx;
begin
Result.sec:= st_mtime;
Result.nanosec:= st_mtimensec;
end;
function TDCStatHelper.atime: TFileTimeEx;
begin
Result.sec:= st_atime;
Result.nanosec:= st_atimensec;
end;
function TDCStatHelper.ctime: TFileTimeEx;
begin
Result.sec:= st_ctime;
Result.nanosec:= st_ctimensec;
end;
{$ELSE}
function TDCStatHelper.birthtime: TFileTimeEx;
begin
Result:= TFileTimeExNull;
end;
function TDCStatHelper.mtime: TFileTimeEx;
begin
Result.sec:= Int64(st_mtime);
Result.nanosec:= Int64(st_mtime_nsec);
end;
function TDCStatHelper.atime: TFileTimeEx;
begin
Result.sec:= Int64(st_atime);
Result.nanosec:= Int64(st_atime_nsec);
end;
function TDCStatHelper.ctime: TFileTimeEx;
begin
Result.sec:= Int64(st_ctime);
Result.nanosec:= Int64(st_ctime_nsec);
end;
{$ENDIF}
{$IF DEFINED(DARWIN)}
Function fpLstat64( path:pchar; Info:pstat ): cint; cdecl; external clib name 'lstat64';
Function DC_fpLstat( const path:RawByteString; var Info:TDCStat ): cint; inline;
var
SystemPath: RawByteString;
begin
SystemPath:=ToSingleByteFileSystemEncodedFileName( path );
Result:= fpLstat64( pchar(SystemPath), @info );
end;
{$ELSE}
Function DC_fpLstat( const path:RawByteString; var Info:TDCStat ): cint; inline;
begin
Result:= fpLstat( path, info );
end;
{$ENDIF}
function fputimes( path:pchar; times:Array of UnixType.timeval ): cint; cdecl; external clib name 'utimes';
function flutimes( path:pchar; times:Array of UnixType.timeval ): cint; cdecl; external clib name 'lutimes';
function DC_FileSetTime(const FileName: String;
const mtime : TFileTimeEx;
const birthtime: TFileTimeEx;
const atime : TFileTimeEx ): Boolean;
var
timevals: Array[0..1] of UnixType.timeval;
begin
Result:= false;
// last access time
timevals[0].tv_sec:= atime.sec;
timevals[0].tv_usec:= round( Extended(atime.nanosec) / 1000.0 );
// last modification time
timevals[1].tv_sec:= mtime.sec;
timevals[1].tv_usec:= round( Extended(mtime.nanosec) / 1000.0 );
if fputimes(pchar(UTF8ToSys(FileName)), timevals) <> 0 then exit;
{$IF not DEFINED(DARWIN)}
Result:= true;
{$ELSE}
Result:= MacosFileSetCreationTime( FileName, birthtime );
{$ENDIF}
end;
// Like DC_FileSetTime but uses lutimes() instead of utimes(), so the
// timestamp is set on the symlink itself rather than its target.
function DC_SymLinkSetTime(const FileName: String;
const mtime : TFileTimeEx;
const atime : TFileTimeEx ): Boolean;
var
timevals: Array[0..1] of UnixType.timeval;
begin
// last access time
timevals[0].tv_sec:= atime.sec;
timevals[0].tv_usec:= round( Extended(atime.nanosec) / 1000.0 );
// last modification time
timevals[1].tv_sec:= mtime.sec;
timevals[1].tv_usec:= round( Extended(mtime.nanosec) / 1000.0 );
Result:= flutimes(pchar(UTF8ToSys(FileName)), timevals) = 0;
end;
{$IF DEFINED(BSD)}
type rlim_t = Int64;
{$ENDIF}
const
{$IF DEFINED(LINUX)}
_SC_OPEN_MAX = 4;
FICLONE = $40049409;
RLIM_INFINITY = rlim_t(-1);
{$ELSEIF DEFINED(BSD)}
_SC_OPEN_MAX = 5;
RLIM_INFINITY = rlim_t(High(QWord) shr 1);
{$ELSEIF DEFINED(HAIKU)}
_SC_OPEN_MAX = 20;
RLIMIT_NOFILE = 4;
RLIM_INFINITY = $ffffffff;
{$ENDIF}
procedure tzset(); cdecl; external clib;
function mktime(tp: PTimeStruct): TTime; cdecl; external clib;
function localtime_r(timer: PTime; tp: PTimeStruct): PTimeStruct; cdecl; external clib;
function lchown(path : PChar; owner : TUid; group : TGid): cInt; cdecl; external clib;
{$IF DEFINED(LINUX)}
function fdatasync(fd: cint): cint; cdecl; external clib;
function fallocate(fd: cint; mode: cint; offset, len: coff_t): cint; cdecl; external clib;
{$ENDIF}
{$IF DEFINED(LINUX) OR DEFINED(FREEBSD)}
var
hLibC: TLibHandle = NilHandle;
procedure LoadCLibrary;
begin
hLibC:= mbLoadLibrary(mbGetModuleName(@tzset));
end;
{$ENDIF}
{$IF DEFINED(LINUX) OR DEFINED(BSD)}
var
close_range: function(first: cuint; last: cuint; flags: cint): cint; cdecl = nil;
{$ENDIF}
procedure FileCloseOnExecAll;
const
MAX_FD = 1024;
var
fd: cint;
p: TRLimit;
fd_max: rlim_t = RLIM_INFINITY;
begin
{$IF DEFINED(LINUX) OR DEFINED(BSD)}
if Assigned(close_range) then
begin
close_range(3, High(Int32), CLOSE_RANGE_CLOEXEC);
Exit;
end;
{$ENDIF}
if (FpGetRLimit(RLIMIT_NOFILE, @p) = 0) and (p.rlim_cur <> RLIM_INFINITY) then
fd_max:= p.rlim_cur
else begin
{$IF DECLARED(_SC_OPEN_MAX)}
fd_max:= sysconf(_SC_OPEN_MAX);
{$ENDIF}
end;
if (fd_max = RLIM_INFINITY) or (fd_max > MAX_FD) then
fd_max:= MAX_FD;
for fd:= 3 to cint(fd_max) do
FileCloseOnExec(fd);
end;
procedure FileCloseOnExec(Handle: System.THandle);
begin
{$IF DECLARED(FD_CLOEXEC)}
FpFcntl(Handle, F_SETFD, FpFcntl(Handle, F_GETFD) or FD_CLOEXEC);
{$ENDIF}
end;
function FindMountPointPath(const FileName: String): String;
var
I, J: LongInt;
sTemp: String;
recStat: Stat;
st_dev: QWord;
begin
// Set root directory as mount point by default
Result:= PathDelim;
// Get stat info for original file
if (fpLStat(FileName, recStat) < 0) then Exit;
// Save device ID of original file
st_dev:= recStat.st_dev;
J:= Length(FileName);
for I:= J downto 1 do
begin
if FileName[I] = PathDelim then
begin
if (I = 1) then
sTemp:= PathDelim
else begin
sTemp:= Copy(FileName, 1, I - 1);
end;
// Stat for current directory
if (fpLStat(sTemp, recStat) < 0) then Continue;
// If it is a link then checking link destination
if fpS_ISLNK(recStat.st_mode) then
begin
sTemp:= mbReadAllLinks(sTemp);
Result:= FindMountPointPath(sTemp);
Exit;
end;
// Check device ID
if (recStat.st_dev <> st_dev) then
begin
Result:= IncludeTrailingPathDelimiter(Copy(FileName, 1, J));
Exit;
end;
J:= I;
end;
end;
end;
function fpLChown(path: String; owner: TUid; group: TGid): cInt;
begin
Result := lchown(PAnsiChar(CeUtf8ToSys(path)), owner, group);
if Result = -1 then fpseterrno(fpgetCerrno);
end;
function FileLock(Handle: System.THandle; Mode: cInt): System.THandle;
var
lockop: cint;
lockres: cint;
lockerr: cint;
{$IFDEF LINUX}
Sbfs: TStatFS;
{$ENDIF}
begin
Result:= Handle;
case (Mode and $F0) of
fmShareCompat,
fmShareExclusive:
lockop:= LOCK_EX or LOCK_NB;
fmShareDenyWrite:
lockop:= LOCK_SH or LOCK_NB;
else
Exit;
end;
{$IFDEF LINUX}
if (fpFStatFS(Handle, @Sbfs) = 0) then
begin
case UInt32(Sbfs.fstype) of
NFS_SUPER_MAGIC,
SMB_SUPER_MAGIC,
SMB2_MAGIC_NUMBER,
CIFS_MAGIC_NUMBER: Exit;
end;
end;
{$ENDIF}
repeat
lockres:= fpFlock(Handle, lockop);
until (lockres = 0) or (fpgeterrno <> ESysEIntr);
lockerr:= fpgeterrno;
{
Only return an error if locks are working and the file was already
locked. Not if locks are simply unsupported (e.g., on Angstrom Linux
you always get ESysNOLCK in the default configuration)
}
if (lockres <> 0) and ((lockerr = ESysEAGAIN) or (lockerr = ESysEDEADLK)) then
begin
Result:= -1;
FileClose(Handle);
end;
end;
function fpMkTime(tm: PTimeStruct): TTime;
begin
Result := mktime(tm);
if (Result = TTime(-1)) then fpseterrno(fpgetCerrno);
end;
function fpLocalTime(timer: PTime; tp: PTimeStruct): PTimeStruct;
begin
Result := localtime_r(timer, tp);
if (Result = nil) then fpseterrno(fpgetCerrno);
end;
{$IF DEFINED(LINUX)}
function fpFDataSync(fd: cint): cint;
begin
Result := fdatasync(fd);
if Result = -1 then fpseterrno(fpgetCerrno);
end;
function fpCloneFile(src_fd, dst_fd: cint): Boolean;
var
ASource: Pointer absolute src_fd;
begin
Result:= (FpIOCtl(dst_fd, FICLONE, ASource) = 0);
end;
function fpFAllocate(fd: cint; mode: cint; offset, len: coff_t): cint;
begin
Result := fallocate(fd, mode, offset, len);
if Result = -1 then fpseterrno(fpgetCerrno);
end;
{$ENDIF}
procedure Initialize;
begin
tzset();
{$IF DEFINED(LINUX) OR DEFINED(FREEBSD)}
LoadCLibrary;
{$IF DEFINED(LINUX)}
KernVersion:= BEtoN(DosVersion);
// Linux kernel >= 5.11
if KernVersion >= $50B then
{$ENDIF}
begin
Pointer(close_range):= GetProcAddress(hLibC, 'close_range');
end;
{$ELSEIF DEFINED(DARWIN)}
close_range:= @CloseRange;
{$ENDIF}
end;
initialization
Initialize;
end.