1
# Copyright 2009 Canonical Ltd.
3
# This file is part of desktopcouch.
5
# desktopcouch is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3
7
# as published by the Free Software Foundation.
9
# desktopcouch is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
16
"Desktop Couch helper files"
18
from __future__ import with_statement
21
import xdg.BaseDirectory
26
from desktopcouch import local_files
29
def process_is_couchdb_linux(pid):
30
"""Find if the process with the given pid is couchdb."""
33
pid = int(pid) # ensure it's a number
34
proc_dir = "/proc/%d" % (pid,)
36
# check to make sure it is actually a desktop-couch instance
37
with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
42
# make sure it's our process.
43
if not os.access(os.path.join(proc_dir, "mem"), os.W_OK):
51
os_name = platform.system()
53
process_is_couchdb = {
54
"Linux": process_is_couchdb_linux
57
raise NotImplementedError("os %r is not yet supported" % (os_name,))
59
def read_pidfile(ctx=local_files.DEFAULT_CONTEXT):
60
"""Read the pid file for the required information."""
62
pid_file = ctx.file_pid
63
if not os.path.exists(pid_file):
65
with open(pid_file) as fp:
69
return None # not yet written to pid file
72
logging.warn("Pid file does not contain int: %r", contents)
75
logging.warn("Reading pid file caused error. %s", e)
79
def find_pid(start_if_not_running=True, ctx=local_files.DEFAULT_CONTEXT):
80
"""Find the current OS process ID of the running couchdb. API users
81
should not use this, and instead go straight to find_port() ."""
82
# Work out whether CouchDB is running by looking at its pid file
83
pid = read_pidfile(ctx=ctx)
84
if not process_is_couchdb(pid):
85
if start_if_not_running:
86
# start CouchDB by running the startup script
87
logging.info("Desktop CouchDB is not running; starting it.")
88
from desktopcouch import start_local_couchdb
89
pid = start_local_couchdb.start_couchdb(ctx=ctx)
90
# now load the design documents and pair records updates,
91
# because it's started
92
start_local_couchdb.update_design_documents()
93
start_local_couchdb.update_pairing_service()
94
if not process_is_couchdb(pid):
95
logging.error("CouchDB process did not start up")
96
raise RuntimeError("desktop-couch not started")
102
def find_port(pid=None, ctx=local_files.DEFAULT_CONTEXT):
103
"""Ask the service daemon through DBUS what the port is. This should start
104
it up if it isn't running."""
106
return _direct_access_find_port(pid=pid, ctx=ctx)
110
#### This fails in multithreaded execution in the client, apparenly
111
#### in DBus. Chad's guess is that there is some collision in getting
112
#### a unique sequence ID for the asynchronous DBus method-call/
113
#### method-response. Once that is worked out, resume using the
114
#### rest of this function instead of the direct access above.
115
# Hrm, we don't use 'pid' or 'ctx' any more, since we go through DBus.
116
if ctx != local_files.DEFAULT_CONTEXT or pid is not None:
117
return _direct_access_find_port(pid=pid, ctx=ctx)
119
bus = dbus.SessionBus()
120
proxy = bus.get_object('org.desktopcouch.CouchDB', '/')
121
return proxy.getPort()
126
def __find_port__linux(pid=None, ctx=local_files.DEFAULT_CONTEXT,
128
"""This returns a valid port or raises a RuntimeError exception. It never
129
returns anything else."""
131
pid = find_pid(start_if_not_running=True, ctx=ctx)
135
return __find_port__linux(pid, ctx, retries_left - 1)
136
raise RuntimeError("Have no PID to use to look up port.")
138
proc_dir = "/proc/%d" % (pid,)
140
# enumerate the process' file descriptors
141
fd_dir = os.path.join(proc_dir, 'fd')
144
for dirent in os.listdir(fd_dir):
146
dirent_path = os.path.join(fd_dir, dirent)
147
fd_paths.append(os.readlink(dirent_path))
149
logging.debug("dirent %r disappeared before " +
150
"we could read it. ", dirent_path)
154
return __find_port__linux(pid, ctx, retries_left - 1)
155
logging.exception("Unable to find file descriptors in %s" % proc_dir)
156
raise RuntimeError("Unable to find file descriptors in %s" % proc_dir)
158
# identify socket fds
159
socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths]
160
# extract their inode numbers
161
socket_inodes = [m.group(1) for m in socket_matches if m is not None]
163
# construct a subexpression which matches any one of these inodes
164
inode_subexp = "|".join(map(re.escape, socket_inodes))
165
# construct regexp to match /proc/net/tcp entries which are listening
166
# sockets having one of the given inode numbers
167
listening_regexp = re.compile(r'''
169
[0-9A-F]{8}: # local_address part 1
170
([0-9A-F]{4})\s+ # local_address part 2
171
00000000:0000\s+ # rem_address
172
0A\s+ # st (0A = listening)
173
[0-9A-F]{8}: # tx_queue
174
[0-9A-F]{8}\s+ # rx_queue
176
[0-9A-F]{8}\s+ # tm->when
177
[0-9A-F]{8}\s* # retrnsmt
178
\d+\s+\d+\s+ # uid, timeout
180
''' % (inode_subexp,), re.VERBOSE)
182
# extract the TCP port from the first matching line in /proc/$pid/net/tcp
184
with open(os.path.join(proc_dir, 'net', 'tcp')) as tcp_file:
185
for line in tcp_file:
186
match = listening_regexp.match(line)
187
if match is not None:
188
port = str(int(match.group(1), 16))
193
return __find_port__linux(pid, ctx, retries_left - 1)
194
raise RuntimeError("Unable to find listening port")
199
os_name = platform.system()
201
_direct_access_find_port = {
202
"Linux": __find_port__linux
205
logging.error("os %r is not yet supported" % (os_name,))
206
raise NotImplementedError("os %r is not yet supported" % (os_name,))