1# frozen_string_literal: false
2require 'socket'
3require_relative 'drb'
4require 'tmpdir'
5
6raise(LoadError, "UNIXServer is required") unless defined?(UNIXServer)
7
8module DRb
9
10  # Implements DRb over a UNIX socket
11  #
12  # DRb UNIX socket URIs look like <code>drbunix:<path>?<option></code>.  The
13  # option is optional.
14
15  class DRbUNIXSocket < DRbTCPSocket
16    # :stopdoc:
17    def self.parse_uri(uri)
18      if /\Adrbunix:(.*?)(\?(.*))?\z/ =~ uri
19        filename = $1
20        option = $3
21        [filename, option]
22      else
23        raise(DRbBadScheme, uri) unless uri.start_with?('drbunix:')
24        raise(DRbBadURI, 'can\'t parse uri:' + uri)
25      end
26    end
27
28    def self.open(uri, config)
29      filename, = parse_uri(uri)
30      filename.untaint
31      soc = UNIXSocket.open(filename)
32      self.new(uri, soc, config)
33    end
34
35    def self.open_server(uri, config)
36      filename, = parse_uri(uri)
37      if filename.size == 0
38        soc = temp_server
39        filename = soc.path
40        uri = 'drbunix:' + soc.path
41      else
42        soc = UNIXServer.open(filename)
43      end
44      owner = config[:UNIXFileOwner]
45      group = config[:UNIXFileGroup]
46      if owner || group
47        require 'etc'
48        owner = Etc.getpwnam( owner ).uid  if owner
49        group = Etc.getgrnam( group ).gid  if group
50        File.chown owner, group, filename
51      end
52      mode = config[:UNIXFileMode]
53      File.chmod(mode, filename) if mode
54
55      self.new(uri, soc, config, true)
56    end
57
58    def self.uri_option(uri, config)
59      filename, option = parse_uri(uri)
60      return "drbunix:#{filename}", option
61    end
62
63    def initialize(uri, soc, config={}, server_mode = false)
64      super(uri, soc, config)
65      set_sockopt(@socket)
66      @server_mode = server_mode
67      @acl = nil
68    end
69
70    # import from tempfile.rb
71    Max_try = 10
72    private
73    def self.temp_server
74      tmpdir = Dir::tmpdir
75      n = 0
76      while true
77        begin
78          tmpname = sprintf('%s/druby%d.%d', tmpdir, $$, n)
79          lock = tmpname + '.lock'
80          unless File.exist?(tmpname) or File.exist?(lock)
81            Dir.mkdir(lock)
82            break
83          end
84        rescue
85          raise "cannot generate tempfile `%s'" % tmpname if n >= Max_try
86          #sleep(1)
87        end
88        n += 1
89      end
90      soc = UNIXServer.new(tmpname)
91      Dir.rmdir(lock)
92      soc
93    end
94
95    public
96    def close
97      return unless @socket
98      shutdown # DRbProtocol#shutdown
99      path = @socket.path if @server_mode
100      @socket.close
101      File.unlink(path) if @server_mode
102      @socket = nil
103      close_shutdown_pipe
104    end
105
106    def accept
107      s = accept_or_shutdown
108      return nil unless s
109      self.class.new(nil, s, @config)
110    end
111
112    def set_sockopt(soc)
113      # no-op for now
114    end
115  end
116
117  DRbProtocol.add_protocol(DRbUNIXSocket)
118  # :startdoc:
119end
120