Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ require "spec"
require "process"
require "tempfile"

DIR_CURRENT = Dir.current

describe Process do
it "runs true" do
process = Process.new("true")
Expand Down Expand Up @@ -215,7 +217,7 @@ describe Process do
end

describe "find_executable" do
pwd = Process::INITIAL_PWD
pwd = DIR_CURRENT
crystal_path = File.join(pwd, "bin", "crystal")

it "resolves absolute executable" do
Expand Down
18 changes: 18 additions & 0 deletions src/crystal/system/darwin/process.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "c/mach-o/dyld"

module System
# nodoc
class Process
def self.executable_path_impl
buf = GC.malloc_atomic(LibC::PATH_MAX).as(UInt8*)
size = LibC::PATH_MAX.to_u32

if LibC._NSGetExecutablePath(buf, pointerof(size)) == -1
buf = GC.malloc_atomic(size).as(UInt8*)
return nil if LibC._NSGetExecutablePath(buf, pointerof(size)) == -1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like repeated call to LibC._NSGetExecutablePath(buf, pointerof(size)). Would it be possible to save result of previous call in a variable and use it here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The first call will report an error and update size to be the actual path length, when the default, arbitrary-sized, buffer is too small. We must make a second call with a larger buffer to get the path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong call, you're right.

end

String.new(buf)
end
end
end
16 changes: 16 additions & 0 deletions src/crystal/system/freebsd/process.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "c/sysctl"

module System
# nodoc
class Process
def self.executable_path_impl
mib = Int32[LibC::CTL_KERN, LibC::KERN_PROC, LibC::KERN_PROC_PATHNAME, -1]
buf = GC.malloc_atomic(LibC::PATH_MAX).as(UInt8*)
size = LibC::SizeT.new(LibC::PATH_MAX)

if LibC.sysctl(mib, 4, buf, pointerof(size), nil, 0) == 0
String.new(buf, size - 1)
end
end
end
end
8 changes: 8 additions & 0 deletions src/crystal/system/linux/process.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module System
# nodoc
class Process
def self.executable_path_impl
"/proc/self/exe"
end
end
end
30 changes: 30 additions & 0 deletions src/crystal/system/process.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Crystal
# :nodoc:
module System
# :nodoc:
module Process
# Returns possible location of executable path
# def self.executable_path_impl
end
end
end

{% if flag?(:darwin) %}
require "./darwin/process"
{% elsif flag?(:freebsd) %}
require "./freebsd/process"
{% elsif flag?(:linux) %}
require "./linux/process"
{% else %}
module System
# nodoc
class Process
INITIAL_PATH = ENV["PATH"]?
INITIAL_PWD = Dir.current

def self.executable_path_impl
::Process.find_executable(PROGRAM_NAME, INITIAL_PATH, INITIAL_PWD)
end
end
end
{% end %}
4 changes: 4 additions & 0 deletions src/lib_c/x86_64-macosx-darwin/c/mach-o/dyld.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
lib LibC
PATH_MAX = 1024
fun _NSGetExecutablePath(buf : Char*, bufsize : UInt32*) : Int
end
60 changes: 3 additions & 57 deletions src/process/executable_path.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
# - https://github.com/gpakosz/whereami/blob/master/src/whereami.c
# - http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe

require "crystal/system/process"

class Process
PATH_DELIMITER = {% if flag?(:windows) %} ';' {% else %} ':' {% end %}

# :nodoc:
INITIAL_PATH = ENV["PATH"]?

# :nodoc:
INITIAL_PWD = Dir.current

# Returns an absolute path to the executable file of the currently running
# program. This is in opposition to `PROGRAM_NAME` which may be a relative or
# absolute path, just the executable file name or a symlink.
Expand All @@ -20,7 +16,7 @@ class Process
#
# Returns `nil` if the file can't be found.
def self.executable_path
if executable = executable_path_impl
if executable = System::Process.executable_path_impl
begin
File.real_path(executable)
rescue Errno
Expand Down Expand Up @@ -50,53 +46,3 @@ class Process
nil
end
end

{% if flag?(:darwin) %}
lib LibC
PATH_MAX = 1024
fun _NSGetExecutablePath(buf : Char*, bufsize : UInt32*) : Int
end

class Process
private def self.executable_path_impl
buf = GC.malloc_atomic(LibC::PATH_MAX).as(UInt8*)
size = LibC::PATH_MAX.to_u32

if LibC._NSGetExecutablePath(buf, pointerof(size)) == -1
buf = GC.malloc_atomic(size).as(UInt8*)
return nil if LibC._NSGetExecutablePath(buf, pointerof(size)) == -1
end

String.new(buf)
end
end

{% elsif flag?(:freebsd) %}
require "c/sysctl"

class Process
private def self.executable_path_impl
mib = Int32[LibC::CTL_KERN, LibC::KERN_PROC, LibC::KERN_PROC_PATHNAME, -1]
buf = GC.malloc_atomic(LibC::PATH_MAX).as(UInt8*)
size = LibC::SizeT.new(LibC::PATH_MAX)

if LibC.sysctl(mib, 4, buf, pointerof(size), nil, 0) == 0
String.new(buf, size - 1)
end
end
end

{% elsif flag?(:linux) %}
class Process
private def self.executable_path_impl
"/proc/self/exe"
end
end

{% else %} # openbsd, ...
class Process
private def self.executable_path_impl
find_executable(PROGRAM_NAME, INITIAL_PATH, INITIAL_PWD)
end
end
{% end %}