'From Cuis 6.0 [latest update: #5588] on 7 December 2022 at 4:30:00 pm'!

!OSProcessAccessor methodsFor: 'file testing' stamp: 'jmv 12/7/2022 16:14:53'!
spaceFor: bytesRequired inDirectoryPath: dirPath
	"Given a file size requirement in bytes, and a path specifying a location
	within a file system, answer true if that file system has sufficient free
	space to save a file of that size. The file size parameter must be a
	SmallInteger (not LargePositiveInteger) and the directory path is a string.
	This primitive is entended as a guard to protect against saving an image
	file when the file system is full."

	"OSProcess accessor spaceFor: 16000000 inDirectoryPath: '/home' "
	"OSProcess accessor spaceFor: 16000000 inDirectoryPath: '/proc' "
	"OSProcess accessor spaceFor: 16000000 inDirectoryPath: 'INVALID PATH' "

	^ self primSpaceFor: bytesRequired InDirectoryPath: dirPath asUtf8BytesOrByteString
! !

!OSProcessAccessor methodsFor: 'system configuration access' stamp: 'jmv 12/7/2022 16:13:27'!
pathconf: path index: optionIndex
	"Answer the result of pathconf() for file path string and the
	supplied option index, an integer value defined in unistd.h. On
	some platforms, the available index values are defined in
	/usr/include/bits/confname.h"

	"OSProcess accessor pathconf: '/bin/bash' index: 0"

	^self primPathconf: path asUtf8BytesOrByteString index: optionIndex
! !


!UnixOSProcessAccessor methodsFor: 'external process access' stamp: 'jmv 12/7/2022 16:09:20'!
chDir: pathString
	"Change current working directory. The $PWD environment variable is not updated (but
	see UnixProcess>>chDir:). Answer nil for success, or an error message."

	"OSProcess accessor chDir: '/tmp'"
	"OSProcess accessor chDir: '/no/such/path'"
	"OSProcess accessor chDir: FileDirectory default name"

	| result message |
	result _ self primChdir: pathString asUtf8BytesOrByteString.
	result isNil
		ifTrue:
			[^ nil]
		ifFalse:
			[message _ self primErrorMessageAt: result.
			^ message]
! !

!UnixOSProcessAccessor methodsFor: 'external process access' stamp: 'jmv 12/7/2022 16:09:53'!
environmentAt: aSymbolOrString
	"Get an environment variable from the external OS process."

	"OSProcess thisOSProcess environmentAt: 'PATH'"

	^ self primEnvironmentAtSymbol: aSymbolOrString asUtf8BytesOrByteString
! !

!UnixOSProcessAccessor methodsFor: 'external process access' stamp: 'jmv 12/7/2022 16:15:48'!
environmentAt: aSymbolOrString put: aString
	"Add or update an environment variable in the external OS process. Convert aSymbol
	and aString into a KEY=value string and pass this to the OS process environment.
	Standard C libraries provide a putenv() function for this purpose, with a parameter
	in the form KEY=value. Note: Maintain a reference to the return value, see note in
	primitivePutEnv."

	"OSProcess accessor environmentAt: 'AAAA' put: 'this is the value of AAAA'"
	"OSProcess accessor environmentAt: 'AAAA' put: nil"
	"OSProcess accessor environmentAt: 'AAAA'"

	aString isNil
		ifTrue:
			[^ self primUnsetEnv: (aSymbolOrString, (Character value: 0) asString) asUtf8BytesOrByteString]
		ifFalse:
			[^ self environmentPut: (aSymbolOrString asString, '=', aString) asUtf8BytesOrByteString]
! !

!UnixOSProcessAccessor methodsFor: 'external process access' stamp: 'jmv 12/7/2022 16:13:54'!
realpath: pathString
	"Get the real path for pathString from the external OS process."

	"OSProcess accessor realpath: '/tmp'"
	"OSProcess accessor realpath: OSProcess defaultPathString"
	"OSProcess accessor realpath: '/bogus/path/name'"

	^ self primRealpath: pathString asUtf8BytesOrByteString
! !

!UnixOSProcessAccessor methodsFor: 'private - primitive access' stamp: 'jmv 12/7/2022 16:13:45'!
environmentPut: aString
	"Add or update an environment variable in the external OS process using a 'KEY=value'
	string. Create a null terminated string for use by the external putenv() call in a pluggable
	primitive."

	"OSProcess thisOSProcess processAccessor environmentPut: 'SOMEKEY=somevalue'"

	| cString |
	cString _ aString, (Character value: 0) asString.
	^ self primPutEnv: cString asUtf8BytesOrByteString
! !

!UnixOSProcessAccessor methodsFor: 'file testing' stamp: 'jmv 12/7/2022 16:10:25'!
fileProtectionMask: aPathString
	"Call stat(2) to obtain the file protection mask for a file."

	"OSProcess accessor fileProtectionMask: '/bin/sh'"
	"OSProcess accessor fileProtectionMask: '/etc/hosts'"
	"OSProcess accessor fileProtectionMask: '/bin/su'"
	"OSProcess accessor fileProtectionMask: '/bin/NOSUCHFILE'"
	"OSProcess accessor fileProtectionMask: 12345"

	| mask |
	(aPathString isKindOf: CharacterSequence) ifFalse: [self error: 'expected a path string'. ^ nil].
	mask _ self primFileProtectionMask: aPathString asUtf8BytesOrByteString.
	(mask == nil) ifTrue: [^ nil].
	(mask isKindOf: Integer)
		ifTrue: [^ self primErrorMessageAt: mask].
	^ mask
! !

!UnixOSProcessAccessor methodsFor: 'file testing' stamp: 'jmv 12/7/2022 16:10:36'!
fileStat: aPathString
	"Call stat(2) to obtain the UID, GID, and file protection mask for a file."

	"OSProcess accessor fileStat: '/var/spool/news'"
	"OSProcess accessor fileStat: '/etc/hosts'"
	"OSProcess accessor fileStat: '/bin/su'"
	"OSProcess accessor fileStat: '/bin/NOSUCHFILE'"
	"OSProcess accessor fileStat: 12345"

	| mask |
	(aPathString isKindOf: CharacterSequence) ifFalse: [self error: 'expected a path string'. ^ nil].
	mask _ self primFileStat: aPathString asUtf8BytesOrByteString.
	(mask == nil) ifTrue: [^ nil].
	(mask isKindOf: Integer)
		ifTrue: [^ self primErrorMessageAt: mask].
	^ mask
! !

!UnixOSProcessAccessor methodsFor: 'fork and exec' stamp: 'jmv 12/7/2022 16:11:58'!
forkAndExec: executableFile
	stdIn: inputFileHandle
	stdOut: outputFileHandle
	stdErr: errorFileHandle
	argBuf: argVec
	argOffsets: argOffsets
	envBuf: envVec
	envOffsets: envOffsets
	workingDir: pathString

	"Parameters are expected to have been properly prepared by the caller, including string
	values which are to be null terminated strings. In other words, all strings should have
	(Character value: 0) as the last element in the string.

	Parameters should be:
		executableFile: a string with the name of a file to execute.
		stdIn: a fileID ByteArray (struct SQFile in C) to be used as standard input.
		stdOut: a fileID ByteArray to be used as standard output.
		stdErr: a fileID ByteArray to be used as standard error.
		argVec: a String arranged to look more or less like a char **, but with the addresses not yet fixed.
		argOffsets: an Array of offsets for fixing up the argVec addresses.
		envVec: a String arranged to look more or less like a char **, but with the addresses not yet fixed.
		envOffsets: an Array of offsets for fixing up the envVec addresses.
		workingDir: a null terminated path name String, or nil.
	The envVec parameter may be nil, in which case envOffsets is ignored. workingDir may be nil. The
	other parameters are required. Parameters with nil value indicate that current values for this process
	should be used."

	^ self primForkExec: executableFile asUtf8BytesOrByteString
			stdIn: inputFileHandle
			stdOut: outputFileHandle
			stdErr: errorFileHandle
			argBuf: argVec asUtf8BytesOrByteString
			argOffsets: argOffsets
			envBuf: envVec
			envOffsets: envOffsets
			workingDir: (pathString ifNotNil: [ :s | s asUtf8BytesOrByteString ])
! !

!UnixOSProcessAccessor methodsFor: 'temp files and directories' stamp: 'jmv 12/7/2022 16:12:43'!
mkdtemp: templateString
	"Call mkdtemp() to generate a uniquely named temporary directory from a template
	string. The last six or more characters of template must be XXXXXX and these are
	replaced with characters that make the directory name unique. The directory is then
	created with permissions 0700. Answer the new directory name."

	"OSProcess accessor mkdtemp: 'foodir.XXXXXX' "

	^self primMkdtemp: templateString asUtf8BytesOrByteString! !

!UnixOSProcessAccessor methodsFor: 'temp files and directories' stamp: 'jmv 12/7/2022 16:13:00'!
mkstemp: templateString

	"Call mkstemp() to generate a unique temporary file from a template string.
	The last six or more characters of template must be XXXXXX and these are
	replaced with a string that make the filename unique. Contents of the template
	string are modified. The file is created with permissions 0600, that is, read plus
	write for owner only. Answer an opened file stream on the newly created file."

	"OSProcess accessor mkstemp: 'foo.XXXXXX' "

	" | s | { s _ 'foo.XXXXXX' . OSProcess accessor mkstemp: s } "

	| fileName sqFile |
	fileName _ templateString asUtf8BytesOrByteString copy.
	sqFile _ self primMkstemp: fileName.
	^ AttachableFileStream
		name: fileName
		attachTo: sqFile
		writable: true
! !


!AttachableFileStream methodsFor: 'attaching' stamp: 'jmv 12/7/2022 16:27:04'!
name: aSymbolOrString attachTo: anIOHandle writable: readWriteFlag
	"Attach to an existing file handle, assumed to have been previously opened by the underlying operating system."

	name _ aSymbolOrString asUtf8BytesOrByteString.
	fileID _ anIOHandle.
	readWriteFlag ifTrue: [self readWrite] ifFalse: [self readOnly].
	self ascii.
	self register.
! !


!UnixProcessAccessorTestCase methodsFor: 'testing - C string arrays' stamp: 'jmv 12/7/2022 16:17:15'!
testArgsAsFlatArrayAndOffsets
	"Test conversion of an array of strings into the form used to create a C **char
	in UnixOSProcessPlugin."

	"(UnixProcessAccessorTestCase selector: #testArgsAsFlatArrayAndOffsets) debug"

	| a pointerSize lengthOfCStrings totalLength stringsAndOffsets rs s arrayWithMachineDependentPointers |
	pointerSize _ OSProcess accessor sizeOfPointer.
	a _ { 'one' . 'two' . 'three' } collect: [ :e | e , (Character value: 0) asString ].
	stringsAndOffsets _ OSProcess thisOSProcess argsAsFlatArrayAndOffsets: a.
	self assert: stringsAndOffsets size == 2.
	self assert: stringsAndOffsets second size == 3.
	lengthOfCStrings _ (a collect: [:e | e size]) sum.
	totalLength _ 4 * pointerSize + lengthOfCStrings.
	self assert: stringsAndOffsets first size == totalLength.
	stringsAndOffsets second do: [:offset |
		(stringsAndOffsets first at: offset) == (Character value: 0)].

	rs _ ReadStream on: stringsAndOffsets first.
	rs next: 4 * pointerSize.
	s _  rs next: 3.
	rs next.
	self assert: s = 'one'.
	s _  rs next: 3.
	rs next.
	self assert: s = 'two'.
	s _  rs next: 5.
	rs next.
	self assert: s = 'three'.

	rs _ ReadStream on: stringsAndOffsets first.
	rs next: (stringsAndOffsets second at: 1).
	s _  rs next: 3.
	self assert: s = 'one'.
	rs _ ReadStream on: stringsAndOffsets first.
	rs next: (stringsAndOffsets second at: 2).
	s _  rs next: 3.
	self assert: s = 'two'.
	rs _ ReadStream on: stringsAndOffsets first.
	rs next: (stringsAndOffsets second at: 3).
	s _  rs next: 5.
	self assert: s = 'three'.

	arrayWithMachineDependentPointers _ self
		primFixPointersInArrayOfStrings: (stringsAndOffsets first collect: [ :ss | ss asUtf8BytesOrByteString ])
		withOffsets: stringsAndOffsets second
		count: stringsAndOffsets second size.
	self assert: arrayWithMachineDependentPointers size == stringsAndOffsets first size.
	rs _ ReadStream on: arrayWithMachineDependentPointers.
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s = (String new: pointerSize). "a NULL pointer"
	self assert: rs upToEnd = (stringsAndOffsets first allButFirst: pointerSize * 4)
! !

!UnixProcessAccessorTestCase methodsFor: 'testing - C string arrays' stamp: 'jmv 12/7/2022 16:21:40'!
testEnvAsFlatArrayAndOffsets
	"Test conversion of a dictionary of key-value environment variables into the
	form used to create a C **char in UnixOSProcessPlugin."

	"(UnixProcessAccessorTestCase selector: #testEnvAsFlatArrayAndOffsets) debug"

	| pointerSize lengthOfCStrings totalLength stringsAndOffsets rs s arrayWithMachineDependentPointers d |
	pointerSize _ OSProcess accessor sizeOfPointer.
	d _ Dictionary new.
	d at: #ONE put: 'one'.
	d at: #TWO put: 'two'.
	d at: #THREE put: 'three'.
	stringsAndOffsets _ OSProcess thisOSProcess envAsFlatArrayAndOffsets: d.
	self assert: stringsAndOffsets size == 2.
	self assert: stringsAndOffsets second size == 3.

	lengthOfCStrings _ d keys
		inject: 0
		into: [:sum :e | sum + e size + '=' size + (d at: e) size + (String new: 1) size].

	totalLength _ 4 * pointerSize + lengthOfCStrings.
	self assert: stringsAndOffsets first size == totalLength.
	stringsAndOffsets second do: [:offset |
		(stringsAndOffsets first at: offset) == (Character value: 0)].

	arrayWithMachineDependentPointers _ self
		primFixPointersInArrayOfStrings: (stringsAndOffsets first collect: [ :ss | ss asUtf8BytesOrByteString ])
		withOffsets: stringsAndOffsets second
		count: stringsAndOffsets second size.
	self assert: arrayWithMachineDependentPointers size == stringsAndOffsets first size.
	rs _ ReadStream on: arrayWithMachineDependentPointers.
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s ~= (String new: pointerSize). "a C pointer"
	s _ rs next: pointerSize.
	self assert: s = (String new: pointerSize). "a NULL pointer"
	self assert: rs upToEnd = (stringsAndOffsets first allButFirst: pointerSize * 4)
! !

!UnixProcessAccessorTestCase methodsFor: 'testing - external process access' stamp: 'jmv 12/7/2022 16:20:59'!
testPrimUnsetEnv

	"(UnixProcessAccessorTestCase selector: #testPrimUnsetEnv) run"

	| oldVal newVal resetVal |
	oldVal _ accessor environmentAt: 'AAAA'.
	newVal _ 'this is a test string'.
	accessor environmentAt: 'AAAA' put: newVal.
	resetVal _ accessor environmentAt: 'AAAA'.
	self should: [resetVal notNil and: [resetVal isEmpty not]].
	self should: [newVal = resetVal].

	accessor primUnsetEnv: ('AAAA', (Character value: 0) asString) asUtf8BytesOrByteString.
	resetVal _ accessor environmentAt: 'AAAA'.
	self should: [resetVal isNil].

	accessor environmentAt: 'AAAA' put: oldVal.
	self should: [oldVal = (accessor environmentAt: 'AAAA')]
! !


!ThisOSProcess methodsFor: 'display management' stamp: 'jmv 12/7/2022 16:07:50'!
canConnectToXDisplay: xDisplayName
	"Open and close a connection to displayName. It the connection was successfully
	opened, answer true; otherwise false. This is intended to check for the ability
	to open an X display prior to actually making the attempt."

	"self thisOSProcess canConnectToXDisplay: ':0.0' "
	"self thisOSProcess canConnectToXDisplay: ':1' "
	"self thisOSProcess canConnectToXDisplay: 'bogus:0' " "<-make sure network is running first!!"

	(xDisplayName isKindOf: CharacterSequence) ifFalse: [^ false].
	^ self processAccessor primCanConnectToXDisplay: xDisplayName asUtf8BytesOrByteString
! !

!ThisOSProcess methodsFor: 'display management' stamp: 'jmv 12/7/2022 16:08:50'!
setXDisplayName: xDisplayName
	"Set X display name for use by the next call to recapitate"

	"self thisOSProcess setXDisplayName: 'unix:0' "
	"self thisOSProcess setXDisplayName: ':1' "

	self processAccessor primSetXDisplayName: (xDisplayName ifNotNil: [xDisplayName asUtf8BytesOrByteString])

! !


!UnixProcess class methodsFor: 'X display - deprecated' stamp: 'jmv 12/7/2022 16:07:55'!
canConnectToXDisplay: xDisplayName
	"Deprecated. See ThisOSProcess>canConnectToXDisplay:"

	"self canConnectToXDisplay: ':0.0' "

	^ self thisOSProcess canConnectToXDisplay: xDisplayName asUtf8BytesOrByteString
! !

