Module: Process

Extended by:
Constants, Functions, Structs
Includes:
Constants
Defined in:
lib/win32/process.rb

Defined Under Namespace

Modules: Constants, Functions, Structs

Constant Summary collapse

WIN32_PROCESS_VERSION =

The version of the win32-process library.

"1.0.0"

Constants included from Constants

Constants::ABOVE_NORMAL_PRIORITY_CLASS, Constants::BELOW_NORMAL_PRIORITY_CLASS, Constants::CREATE_BREAKAWAY_FROM_JOB, Constants::CREATE_DEFAULT_ERROR_MODE, Constants::CREATE_NEW_CONSOLE, Constants::CREATE_NEW_PROCESS_GROUP, Constants::CREATE_NO_WINDOW, Constants::CREATE_PRESERVE_CODE_AUTHZ_LEVEL, Constants::CREATE_PROTECTED_PROCESS, Constants::CREATE_SEPARATE_WOW_VDM, Constants::CREATE_SHARED_WOW_VDM, Constants::CREATE_SUSPENDED, Constants::CREATE_UNICODE_ENVIRONMENT, Constants::CTRL_BREAK_EVENT, Constants::CTRL_C_EVENT, Constants::DEBUG_ONLY_THIS_PROCESS, Constants::DEBUG_PROCESS, Constants::DETACHED_PROCESS, Constants::ERROR_ACCESS_DENIED, Constants::ERROR_NO_MORE_FILES, Constants::HANDLE_FLAG_INHERIT, Constants::HIGH_PRIORITY_CLASS, Constants::IDLE_PRIORITY_CLASS, Constants::INFINITE, Constants::INHERIT_PARENT_AFFINITY, Constants::INVALID_HANDLE_VALUE, Constants::JOB_OBJECT_LIMIT_PROCESS_MEMORY, Constants::JOB_OBJECT_LIMIT_PROCESS_TIME, Constants::JOB_OBJECT_QUERY, Constants::JOB_OBJECT_SET_ATTRIBUTES, Constants::JobObjectExtendedLimitInformation, Constants::LOGON_NETCREDENTIALS_ONLY, Constants::LOGON_WITH_PROFILE, Constants::NORMAL_PRIORITY_CLASS, Constants::PRIO_PGRP, Constants::PRIO_PROCESS, Constants::PRIO_USER, Constants::PROCESS_ALL_ACCESS, Constants::PROCESS_QUERY_INFORMATION, Constants::PROCESS_SET_INFORMATION, Constants::PROCESS_TERMINATE, Constants::PROCESS_VM_READ, Constants::REALTIME_PRIORITY_CLASS, Constants::RLIMIT_AS, Constants::RLIMIT_CPU, Constants::RLIMIT_FSIZE, Constants::RLIMIT_RSS, Constants::RLIMIT_VMEM, Constants::SEM_FAILCRITICALERRORS, Constants::SEM_NOGPFAULTERRORBOX, Constants::SHUTDOWN_NORETRY, Constants::STARTF_FORCEOFFFEEDBACK, Constants::STARTF_FORCEONFEEDBACK, Constants::STARTF_PREVENTPINNING, Constants::STARTF_RUNFULLSCREEN, Constants::STARTF_TITLEISAPPID, Constants::STARTF_TITLEISLINKNAME, Constants::STARTF_USECOUNTCHARS, Constants::STARTF_USEFILLATTRIBUTE, Constants::STARTF_USEHOTKEY, Constants::STARTF_USEPOSITION, Constants::STARTF_USESHOWWINDOW, Constants::STARTF_USESIZE, Constants::STARTF_USESTDHANDLES, Constants::STILL_ACTIVE, Constants::TH32CS_SNAPHEAPLIST, Constants::TH32CS_SNAPMODULE, Constants::TH32CS_SNAPPROCESS, Constants::TH32CS_SNAPTHREAD, Constants::TOKEN_QUERY, Constants::TokenUser

Class Method Summary collapse

Class Method Details

.create(args) ⇒ Object

Process.create(key => value, …) => ProcessInfo

This is a wrapper for the CreateProcess() function. It executes a process, returning a ProcessInfo struct. It accepts a hash as an argument. There are several primary keys:

  • command_line (this or app_name must be present)

  • app_name (default: nil)

  • inherit (default: false)

  • process_inherit (default: false)

  • thread_inherit (default: false)

  • creation_flags (default: 0)

  • cwd (default: Dir.pwd)

  • startup_info (default: nil)

  • environment (default: nil)

  • close_handles (default: true)

  • with_logon (default: nil)

  • domain (default: nil)

  • password (default: nil, mandatory if with_logon)

Of these, the ‘command_line’ or ‘app_name’ must be specified or an error is raised. Both may be set individually, but ‘command_line’ should be preferred if only one of them is set because it does not (necessarily) require an explicit path or extension to work.

The ‘domain’ and ‘password’ options are only relevent in the context of ‘with_logon’. If ‘with_logon’ is set, then the ‘password’ option is mandatory.

The startup_info key takes a hash. Its keys are attributes that are part of the StartupInfo struct, and are generally only meaningful for GUI or console processes. See the documentation on CreateProcess() and the StartupInfo struct on MSDN for more information.

  • desktop

  • title

  • x

  • y

  • x_size

  • y_size

  • x_count_chars

  • y_count_chars

  • fill_attribute

  • sw_flags

  • startf_flags

  • stdin

  • stdout

  • stderr

Note that the ‘stdin’, ‘stdout’ and ‘stderr’ options can be either Ruby IO objects or file descriptors (i.e. a fileno). However, StringIO objects are not currently supported. Unfortunately, setting these is not currently an option for JRuby.

If ‘stdin’, ‘stdout’ or ‘stderr’ are specified, then the inherit value is automatically set to true and the Process::STARTF_USESTDHANDLES flag is automatically OR’d to the startf_flags value.

The ProcessInfo struct contains the following members:

  • process_handle - The handle to the newly created process.

  • thread_handle - The handle to the primary thread of the process.

  • process_id - Process ID.

  • thread_id - Thread ID.

If the ‘close_handles’ option is set to true (the default) then the process_handle and the thread_handle are automatically closed for you before the ProcessInfo struct is returned.

If the ‘with_logon’ option is set, then the process runs the specified executable file in the security context of the specified credentials.

To simulate Process.wait you can use this approach:

sleep 0.1 while !Process.get_exitcode(info.process_id)

If you really to use Process.wait, then you should use the Process.spawn method instead of Process.create where possible.



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/win32/process.rb', line 472

def create(args)
  unless args.is_a?(Hash)
    raise TypeError, "hash keyword arguments expected"
  end

  valid_keys = %w{
    app_name command_line inherit creation_flags cwd environment
    startup_info thread_inherit process_inherit close_handles with_logon
    domain password
  }

  valid_si_keys = %w{
    startf_flags desktop title x y x_size y_size x_count_chars
    y_count_chars fill_attribute sw_flags stdin stdout stderr
  }

  # Set default values
  hash = {
    "app_name"       => nil,
    "creation_flags" => 0,
    "close_handles"  => true,
  }

  # Validate the keys, and convert symbols and case to lowercase strings.
  args.each { |key, val|
    key = key.to_s.downcase
    unless valid_keys.include?(key)
      raise ArgumentError, "invalid key '#{key}'"
    end

    hash[key] = val
  }

  si_hash = {}

  # If the startup_info key is present, validate its subkeys
  if hash["startup_info"]
    hash["startup_info"].each { |key, val|
      key = key.to_s.downcase
      unless valid_si_keys.include?(key)
        raise ArgumentError, "invalid startup_info key '#{key}'"
      end

      si_hash[key] = val
    }
  end

  # The +command_line+ key is mandatory unless the +app_name+ key
  # is specified.
  unless hash["command_line"]
    if hash["app_name"]
      hash["command_line"] = hash["app_name"]
      hash["app_name"] = nil
    else
      raise ArgumentError, "command_line or app_name must be specified"
    end
  end

  env = nil

  # The env string should be passed as a string of ';' separated paths.
  if hash["environment"]
    env = hash["environment"]

    unless env.respond_to?(:join)
      env = hash["environment"].split(File::PATH_SEPARATOR)
    end

    env = env.map { |e| e + 0.chr }.join("") + 0.chr
    env.to_wide_string! if hash["with_logon"]
  end

  # Process SECURITY_ATTRIBUTE structure
  process_security = nil

  if hash["process_inherit"]
    process_security = SECURITY_ATTRIBUTES.new
    process_security[:nLength] = 12
    process_security[:bInheritHandle] = 1
  end

  # Thread SECURITY_ATTRIBUTE structure
  thread_security = nil

  if hash["thread_inherit"]
    thread_security = SECURITY_ATTRIBUTES.new
    thread_security[:nLength] = 12
    thread_security[:bInheritHandle] = 1
  end

  # Automatically handle stdin, stdout and stderr as either IO objects
  # or file descriptors. This won't work for StringIO, however. It also
  # will not work on JRuby because of the way it handles internal file
  # descriptors.
  #
  %w{stdin stdout stderr}.each { |io|
    if si_hash[io]
      if si_hash[io].respond_to?(:fileno)
        handle = get_osfhandle(si_hash[io].fileno)
      else
        handle = get_osfhandle(si_hash[io])
      end

      if handle == INVALID_HANDLE_VALUE
        ptr = FFI::MemoryPointer.new(:int)

        if windows_version >= 6 && get_errno(ptr) == 0
          errno = ptr.read_int
        else
          errno = FFI.errno
        end

        raise SystemCallError.new("get_osfhandle", errno)
      end

      # Most implementations of Ruby on Windows create inheritable
      # handles by default, but some do not. RF bug #26988.
      bool = SetHandleInformation(
        handle,
        HANDLE_FLAG_INHERIT,
        HANDLE_FLAG_INHERIT
      )

      raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool

      si_hash[io] = handle
      si_hash["startf_flags"] ||= 0
      si_hash["startf_flags"] |= STARTF_USESTDHANDLES
      hash["inherit"] = true
    end
  }

  procinfo  = PROCESS_INFORMATION.new
  startinfo = STARTUPINFO.new

  unless si_hash.empty?
    startinfo[:cb]              = startinfo.size
    startinfo[:lpDesktop]       = si_hash["desktop"] if si_hash["desktop"]
    startinfo[:lpTitle]         = si_hash["title"] if si_hash["title"]
    startinfo[:dwX]             = si_hash["x"] if si_hash["x"]
    startinfo[:dwY]             = si_hash["y"] if si_hash["y"]
    startinfo[:dwXSize]         = si_hash["x_size"] if si_hash["x_size"]
    startinfo[:dwYSize]         = si_hash["y_size"] if si_hash["y_size"]
    startinfo[:dwXCountChars]   = si_hash["x_count_chars"] if si_hash["x_count_chars"]
    startinfo[:dwYCountChars]   = si_hash["y_count_chars"] if si_hash["y_count_chars"]
    startinfo[:dwFillAttribute] = si_hash["fill_attribute"] if si_hash["fill_attribute"]
    startinfo[:dwFlags]         = si_hash["startf_flags"] if si_hash["startf_flags"]
    startinfo[:wShowWindow]     = si_hash["sw_flags"] if si_hash["sw_flags"]
    startinfo[:cbReserved2]     = 0
    startinfo[:hStdInput]       = si_hash["stdin"] if si_hash["stdin"]
    startinfo[:hStdOutput]      = si_hash["stdout"] if si_hash["stdout"]
    startinfo[:hStdError]       = si_hash["stderr"] if si_hash["stderr"]
  end

  app = nil
  cmd = nil

  # Convert strings to wide character strings if present
  if hash["app_name"]
    app = hash["app_name"].to_wide_string
  end

  if hash["command_line"]
    cmd = hash["command_line"].to_wide_string
  end

  if hash["cwd"]
    cwd = hash["cwd"].to_wide_string
  end

  if hash["with_logon"]
    logon = hash["with_logon"].to_wide_string

    if hash["password"]
      passwd = hash["password"].to_wide_string
    else
      raise ArgumentError, "password must be specified if with_logon is used"
    end

    if hash["domain"]
      domain = hash["domain"].to_wide_string
    end

    hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT

    bool = CreateProcessWithLogonW(
      logon,                  # User
      domain,                 # Domain
      passwd,                 # Password
      LOGON_WITH_PROFILE,     # Logon flags
      app,                    # App name
      cmd,                    # Command line
      hash["creation_flags"], # Creation flags
      env,                    # Environment
      cwd,                    # Working directory
      startinfo,              # Startup Info
      procinfo                # Process Info
    )

    unless bool
      raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
    end
  else
    inherit = hash["inherit"] ? 1 : 0

    bool = CreateProcessW(
      app,                    # App name
      cmd,                    # Command line
      process_security,       # Process attributes
      thread_security,        # Thread attributes
      inherit,                # Inherit handles?
      hash["creation_flags"], # Creation flags
      env,                    # Environment
      cwd,                    # Working directory
      startinfo,              # Startup Info
      procinfo                # Process Info
    )

    unless bool
      raise SystemCallError.new("CreateProcess", FFI.errno)
    end
  end

  # Automatically close the process and thread handles in the
  # PROCESS_INFORMATION struct unless explicitly told not to.
  if hash["close_handles"]
    CloseHandle(procinfo[:hProcess])
    CloseHandle(procinfo[:hThread])
  end

  ProcessInfo.new(
    procinfo[:hProcess],
    procinfo[:hThread],
    procinfo[:dwProcessId],
    procinfo[:dwThreadId]
  )
end

.get_affinity(int = Process.pid) ⇒ Object

Returns the process and system affinity mask for the given pid, or the current process if no pid is provided. The return value is a two element array, with the first containing the process affinity mask, and the second containing the system affinity mask. Both are decimal values.

A process affinity mask is a bit vector indicating the processors that a process is allowed to run on. A system affinity mask is a bit vector in which each bit represents the processors that are configured into a system.

Example:

# System has 4 processors, current process is allowed to run on all.
Process.get_affinity # => [[15], [15]], where '15' is 1 + 2 + 4 + 8

# System has 4 processors, current process only allowed on 1 and 4.
Process.get_affinity # => [[9], [15]]

If you want to convert a decimal bit vector into an array of 0’s and 1’s indicating the flag value of each processor, you can use something like this approach:

mask = Process.get_affinity.first
(0..mask).to_a.map{ |n| mask[n] }


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/win32/process.rb', line 51

def get_affinity(int = Process.pid)
  pmask = FFI::MemoryPointer.new(:ulong)
  smask = FFI::MemoryPointer.new(:ulong)

  if int == Process.pid
    unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
      raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
    end
  else
    begin
      handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)

      if handle == 0
        raise SystemCallError, FFI.errno, "OpenProcess"
      end

      unless GetProcessAffinityMask(handle, pmask, smask)
        raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
      end
    ensure
      CloseHandle(handle)
    end
  end

  [pmask.read_ulong, smask.read_ulong]
end

.get_exitcode(pid) ⇒ Object

Returns the exitcode of the process with given pid or nil if the process is still running. Note that the process doesn’t have to be a child process.

This method is very handy for finding out if a process started with Process.create is still running. The usual way of calling Process.wait doesn’t work when the process isn’t recognized as a child process (ECHILD). This happens for example when stdin, stdout or stderr are set to custom values.



895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
# File 'lib/win32/process.rb', line 895

def get_exitcode(pid)
  handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid)

  if handle == INVALID_HANDLE_VALUE
    raise SystemCallError.new("OpenProcess", FFI.errno)
  end

  begin
    buf = FFI::MemoryPointer.new(:ulong, 1)

    unless GetExitCodeProcess(handle, buf)
      raise SystemCallError.new("GetExitCodeProcess", FFI.errno)
    end
  ensure
    CloseHandle(handle)
  end

  exitcode = buf.read_int

  if exitcode == STILL_ACTIVE
    nil
  else
    exitcode
  end
end

.getpriority(kind, int) ⇒ Object

Retrieves the priority class for the specified process id int. Unlike the default implementation, lower return values do not necessarily correspond to higher priority classes.

The kind parameter is ignored but required for API compatibility. You can only retrieve process information, not process group or user information, so it is effectively always Process::PRIO_PROCESS.

Possible return values are:

32 => Process::NORMAL_PRIORITY_CLASS 64 => Process::IDLE_PRIORITY_CLASS 128 => Process::HIGH_PRIORITY_CLASS 256 => Process::REALTIME_PRIORITY_CLASS 16384 => Process::BELOW_NORMAL_PRIORITY_CLASS 32768 => Process::ABOVE_NORMAL_PRIORITY_CLASS

Raises:

  • (TypeError)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/win32/process.rb', line 97

def getpriority(kind, int)
  raise TypeError, kind unless kind.is_a?(Integer) # Match spec
  raise TypeError, int unless int.is_a?(Integer)   # Match spec

  int = Process.pid if int == 0 # Match spec

  handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, int)

  if handle == 0
    raise SystemCallError, FFI.errno, "OpenProcess"
  end

  begin
    priority = GetPriorityClass(handle)

    if priority == 0
      raise SystemCallError, FFI.errno, "GetPriorityClass"
    end
  ensure
    CloseHandle(handle)
  end

  priority
end

.getrlimit(resource) ⇒ Object

Gets the resource limit of the current process. Only a limited number of flags are supported.

Process::RLIMIT_CPU Process::RLIMIT_FSIZE Process::RLIMIT_AS Process::RLIMIT_RSS Process::RLIMIT_VMEM

The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants all refer to the Process memory limit. The Process::RLIMIT_CPU constant refers to the per process user time limit. The Process::RLIMIT_FSIZE constant is hard coded to the maximum file size on an NTFS filesystem, approximately 4TB (or 4GB if not NTFS).

While a two element array is returned in order to comply with the spec, there is no separate hard and soft limit. The values will always be the same.

If [0,0] is returned then it means no limit has been set.

Example:

Process.getrlimit(Process::RLIMIT_VMEM) # => [0, 0]

– NOTE: Both the getrlimit and setrlimit method use an at_exit handler to close a job handle. This is necessary because simply calling it at the end of the block, while marking it for closure, would also make it unavailable within the same process again since it would no longer be associated with the job. In other words, trying to call it more than once within the same program would fail.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/win32/process.rb', line 247

def getrlimit(resource)
  if resource == RLIMIT_FSIZE
    if volume_type == "NTFS"
      return ((1024**4) * 4) - (1024 * 64) # ~ 4TB
    else
      return (1024**3) * 4 # 4 GB
    end
  end

  handle = nil
  in_job = Process.job?

  # Put the current process in a job if it's not already in one
  if in_job && defined?(@win32_process_job_name)
    handle = OpenJobObjectA(JOB_OBJECT_QUERY, 1, @win32_process_job_name)
    raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
  else
    @win32_process_job_name = "ruby_" + Process.pid.to_s
    handle = CreateJobObjectA(nil, @win32_process_job_name)
    raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
  end

  begin
    unless in_job
      unless AssignProcessToJobObject(handle, GetCurrentProcess())
        raise Error, get_last_error
      end
    end

    ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
    val = nil

    # Set the LimitFlags member of the struct
    case resource
      when RLIMIT_CPU
        ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
      when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
        ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
      else
        raise ArgumentError, "unsupported resource type: '#{resource}'"
    end

    bool = QueryInformationJobObject(
      handle,
      JobObjectExtendedLimitInformation,
      ptr,
      ptr.size,
      nil
    )

    unless bool
      raise SystemCallError, FFI.errno, "QueryInformationJobObject"
    end

    case resource
      when Process::RLIMIT_CPU
        val = ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart]
      when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
        val = ptr[:ProcessMemoryLimit]
    end

  ensure
    at_exit { CloseHandle(handle) if handle }
  end

  [val, val]
end

.job?Boolean

Returns whether or not the current process is part of a Job (process group).

Returns:

  • (Boolean)


20
21
22
23
24
# File 'lib/win32/process.rb', line 20

def job?
  pbool = FFI::MemoryPointer.new(:int)
  IsProcessInJob(GetCurrentProcess(), nil, pbool)
  pbool.read_int == 1 ? true : false
end

.kill(signal, *pids) ⇒ Object

Kill a given process with a specific signal. This overrides the default implementation of Process.kill. The differences mainly reside in the way it kills processes, but this version also gives you finer control over behavior.

Internally, signals 2 and 3 will generate a console control event, using a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the process harshly, given that process no chance to do any internal cleanup. Signals 1 and 4-8 kill the process more nicely, giving the process a chance to do internal cleanup before being killed. Signal 0 behaves the same as the default implementation.

When using signals 1 or 4-8 you may specify additional options that allow finer control over how that process is killed and how your program behaves.

Possible options for signals 1 and 4-8.

:exit_proc => The name of the exit function called when signal 1 or 4-8

is used. The default is 'ExitProcess'.

:dll_module => The name of the .dll (or .exe) that contains :exit_proc.

The default is 'kernel32'.

:wait_time => The time, in milliseconds, to wait for the process to

actually die. The default is 5ms. If you specify 0 here
then the process does not wait if the process is not
signaled and instead returns immediately. Alternatively,
you may specify Process::INFINITE, and your code will
block until the process is actually signaled.

Example:

Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')


747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
# File 'lib/win32/process.rb', line 747

def kill(signal, *pids)
  # Match the spec, require at least 2 arguments
  if pids.length == 0
    raise ArgumentError, "wrong number of arguments (1 for at least 2)"
  end

  # Match the spec, signal may not be less than zero if numeric
  if signal.is_a?(Numeric) && signal < 0 # EINVAL
    raise SystemCallError.new(22)
  end

  # Match the spec, signal must be a numeric, string or symbol
  unless signal.is_a?(String) || signal.is_a?(Numeric) || signal.is_a?(Symbol)
    raise ArgumentError, "bad signal type #{signal.class}"
  end

  # Match the spec, making an exception for BRK/SIGBRK, if the signal name is invalid.
  # Older versions of JRuby did not include KILL, so we've made an explicit exception
  # for that here, too.
  if signal.is_a?(String) || signal.is_a?(Symbol)
    signal = signal.to_s.sub("SIG", "")
    unless Signal.list.keys.include?(signal) || %w{KILL BRK}.include?(signal)
      raise ArgumentError, "unsupported name '#{signal}'"
    end
  end

  # If the last argument is a hash, pop it and assume it's a hash of options
  if pids.last.is_a?(Hash)
    hash = pids.pop
    opts = {}

    valid = %w{exit_proc dll_module wait_time}

    hash.each { |k, v|
      k = k.to_s.downcase
      unless valid.include?(k)
        raise ArgumentError, "invalid option '#{k}'"
      end

      opts[k] = v
    }

    exit_proc  = opts["exit_proc"]  || "ExitProcess"
    dll_module = opts["dll_module"] || "kernel32"
    wait_time  = opts["wait_time"]  || 5
  else
    wait_time  = 5
    exit_proc  = "ExitProcess"
    dll_module = "kernel32"
  end

  count = 0

  pids.each { |pid|
    raise TypeError unless pid.is_a?(Numeric) # Match spec, pid must be a number
    raise SystemCallError.new(22) if pid < 0  # Match spec, EINVAL if pid less than zero

    sigint = [Signal.list["INT"], "INT", "SIGINT", :INT, :SIGINT, 2]

    # Match the spec
    if pid == 0 && !sigint.include?(signal)
      raise SystemCallError.new(22)
    end

    if signal == 0
      access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
    elsif signal == 9
      access = PROCESS_TERMINATE
    else
      access = PROCESS_ALL_ACCESS
    end

    begin
      handle = OpenProcess(access, 0, pid)

      if signal != 0 && handle == 0
        raise SystemCallError, FFI.errno, "OpenProcess"
      end

      case signal
        when 0
          if handle != 0
            count += 1
          else
            if FFI.errno == ERROR_ACCESS_DENIED
              count += 1
            else
              raise SystemCallError.new(3) # ESRCH
            end
          end
        when Signal.list["INT"], "INT", "SIGINT", :INT, :SIGINT, 2
          if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
            count += 1
          else
            raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
          end
        when Signal.list["BRK"], "BRK", "SIGBRK", :BRK, :SIGBRK, 3
          if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
            count += 1
          else
            raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
          end
        when Signal.list["KILL"], "KILL", "SIGKILL", :KILL, :SIGKILL, 9
          if TerminateProcess(handle, pid)
            count += 1
          else
            raise SystemCallError.new("TerminateProcess", FFI.errno)
          end
        else
          thread_id = FFI::MemoryPointer.new(:ulong)

          mod = GetModuleHandle(dll_module)

          if mod == 0
            raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
          end

          proc_addr = GetProcAddress(mod, exit_proc)

          if proc_addr == 0
            raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
          end

          thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)

          if thread > 0
            WaitForSingleObject(thread, wait_time)
            count += 1
          else
            raise SystemCallError.new("CreateRemoteThread", FFI.errno)
          end
      end
    ensure
      CloseHandle(handle) if handle
    end
  }

  count
end

.setpriority(kind, int, int_priority) ⇒ Object

Sets the priority class for the specified process id int.

The kind parameter is ignored but present for API compatibility. You can only retrieve process information, not process group or user information, so it is effectively always Process::PRIO_PROCESS.

Possible int_priority values are:

  • Process::NORMAL_PRIORITY_CLASS

  • Process::IDLE_PRIORITY_CLASS

  • Process::HIGH_PRIORITY_CLASS

  • Process::REALTIME_PRIORITY_CLASS

  • Process::BELOW_NORMAL_PRIORITY_CLASS

  • Process::ABOVE_NORMAL_PRIORITY_CLASS

Raises:

  • (TypeError)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/win32/process.rb', line 139

def setpriority(kind, int, int_priority)
  raise TypeError unless kind.is_a?(Integer)          # Match spec
  raise TypeError unless int.is_a?(Integer)           # Match spec
  raise TypeError unless int_priority.is_a?(Integer)  # Match spec

  int = Process.pid if int == 0                       # Match spec

  handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)

  if handle == 0
    raise SystemCallError, FFI.errno, "OpenProcess"
  end

  begin
    unless SetPriorityClass(handle, int_priority)
      raise SystemCallError, FFI.errno, "SetPriorityClass"
    end
  ensure
    CloseHandle(handle)
  end

  0 # Match the spec
end

.setrlimit(resource, current_limit, max_limit = nil) ⇒ Object

Sets the resource limit of the current process. Only a limited number of flags are supported.

Process::RLIMIT_CPU Process::RLIMIT_AS Process::RLIMIT_RSS Process::RLIMIT_VMEM

The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants all refer to the Process memory limit. The Process::RLIMIT_CPU constant refers to the per process user time limit.

The max_limit parameter is provided for interface compatibility only. It is always set to the current_limit value.

Example:

Process.setrlimit(Process::RLIMIT_VMEM, 1024 * 4) # => nil
Process.getrlimit(Process::RLIMIT_VMEM) # => [4096, 4096]

WARNING: Exceeding the limit you set with this method could segfault the interpreter. Consider this method experimental.



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/win32/process.rb', line 340

def setrlimit(resource, current_limit, max_limit = nil)
  max_limit = current_limit

  handle = nil
  in_job = Process.job?

  unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
    raise ArgumentError, "unsupported resource type: '#{resource}'"
  end

  # Put the current process in a job if it's not already in one
  if in_job && defined? @win32_process_job_name
    handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, 1, @win32_process_job_name)
    raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
  else
    @win32_process_job_name = "ruby_" + Process.pid.to_s
    handle = CreateJobObjectA(nil, @win32_process_job_name)
    raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
  end

  begin
    unless in_job
      unless AssignProcessToJobObject(handle, GetCurrentProcess())
        raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
      end
    end

    ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new

    # Set the LimitFlags and relevant members of the struct
    if resource == RLIMIT_CPU
      ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
      ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
    else
      ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
      ptr[:ProcessMemoryLimit] = max_limit
    end

    bool = SetInformationJobObject(
      handle,
      JobObjectExtendedLimitInformation,
      ptr,
      ptr.size
    )

    unless bool
      raise SystemCallError, FFI.errno, "SetInformationJobObject"
    end
  ensure
    at_exit { CloseHandle(handle) if handle }
  end
end

.snapshot(info_type = "thread") ⇒ Object

Returns a list of process information structs in the form of a hash, with the pid as the key, and an array of information as the value of that key. The type of information in that array depends on the info_type parameter. The possible values for info_type, and the type of information they each return is as follows:

:thread  => ThreadSnapInfo[:thread_id, :process_id, :base_priority]
:heap    => HeapSnapInfo[:address, :block_size, :flags, :process_id, :heap_id]
:module  => ModuleSnapInfo[:process_id, :address, :module_size, :handle, :name, :path]
:process => ProcessSnapInfo[:process_id, :threads, :parent_process_id, :priority, :flags, :path]

If no argument is provided, then :thread is assumed. Note that it is up to you to filter by pid if you wish.

Example:

# Get all thread info
Process.snapshot.each{ |pid, v|
  puts "PID: #{pid}"
  p v
}

# Get module info for just the current process
p Process.snapshot(:module)[Process.pid]

# Get heap info for just the current process
p Process.snapshot(:heap)[Process.pid]

# Show pids of all running processes
p Process.snapshot(:process).keys


952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
# File 'lib/win32/process.rb', line 952

def snapshot(info_type = "thread")
  case info_type.to_s.downcase
    when "thread"
      flag = TH32CS_SNAPTHREAD
    when "heap"
      flag = TH32CS_SNAPHEAPLIST
    when "module"
      flag = TH32CS_SNAPMODULE
    when "process"
      flag = TH32CS_SNAPPROCESS
    else
      raise ArgumentError, "info_type '#{info_type}' unsupported"
  end

  begin
    handle = CreateToolhelp32Snapshot(flag, Process.pid)

    if handle == INVALID_HANDLE_VALUE
      raise SystemCallError.new("CreateToolhelp32Snapshot", FFI.errno)
    end

    case info_type.to_s.downcase
      when "thread"
        array = get_thread_info(handle)
      when "heap"
        array = get_heap_info(handle)
      when "module"
        array = get_module_info(handle)
      when "process"
        array = get_process_info(handle)
    end

    array
  ensure
    CloseHandle(handle) if handle
  end
end

.uid(sid = false) ⇒ Object

Returns the uid of the current process. Specifically, it returns the RID of the SID associated with the owner of the process.

If sid is set to true, then a binary sid is returned. Otherwise, a numeric id is returned (the default). – The Process.uid method in core Ruby always returns 0 on MS Windows.

Raises:

  • (TypeError)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/win32/process.rb', line 173

def uid(sid = false)
  token = FFI::MemoryPointer.new(:ulong)

  raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)

  unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
    raise SystemCallError, FFI.errno, "OpenProcessToken"
  end

  token   = token.read_ulong
  rlength = FFI::MemoryPointer.new(:ulong)
  tuser   = 0.chr * 512

  bool = GetTokenInformation(
    token,
    TokenUser,
    tuser,
    tuser.size,
    rlength
  )

  unless bool
    raise SystemCallError, FFI.errno, "GetTokenInformation"
  end

  string_sid = tuser[FFI.type_size(:pointer) * 2, (rlength.read_ulong - FFI.type_size(:pointer) * 2)]

  if sid
    string_sid
  else
    psid = FFI::MemoryPointer.new(:uintptr_t)

    unless ConvertSidToStringSidA(string_sid, psid)
      raise SystemCallError, FFI.errno, "ConvertSidToStringSid"
    end

    psid.read_pointer.read_string.split("-").last.to_i
  end
end