posix: move server side of unix domain sockets out of inotify
authorBryan O'Sullivan <bryano@fb.com>
Tue, 18 Dec 2012 17:15:13 -0800
changeset 18097 ae54cff742e2
parent 18096 cd53e40ab0e2
child 18098 8c4e4661a0cb
posix: move server side of unix domain sockets out of inotify We also turn the unix domain socket into a class, so that we have a sensible place to hang its logically related attributes and behaviour. We'll shortly want to reuse this in other code.
hgext/inotify/linuxserver.py
hgext/inotify/server.py
mercurial/posix.py
--- a/hgext/inotify/linuxserver.py	Tue Dec 18 17:33:32 2012 -0800
+++ b/hgext/inotify/linuxserver.py	Tue Dec 18 17:15:13 2012 -0800
@@ -405,14 +405,7 @@
 
     def shutdown(self):
         self.sock.close()
-        try:
-            os.unlink(self.sockpath)
-            if self.realsockpath:
-                os.unlink(self.realsockpath)
-                os.rmdir(os.path.dirname(self.realsockpath))
-        except OSError, err:
-            if err.errno != errno.ENOENT:
-                raise
+        self.sock.cleanup()
 
     def answer_stat_query(self, cs):
         if self.repowatcher.timeout:
--- a/hgext/inotify/server.py	Tue Dec 18 17:33:32 2012 -0800
+++ b/hgext/inotify/server.py	Tue Dec 18 17:15:13 2012 -0800
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from mercurial.i18n import _
-from mercurial import cmdutil, osutil, util
+from mercurial import cmdutil, posix, osutil, util
 import common
 
 import errno
@@ -330,41 +330,15 @@
     def __init__(self, ui, root, repowatcher, timeout):
         self.ui = ui
         self.repowatcher = repowatcher
-        self.sock = socket.socket(socket.AF_UNIX)
-        self.sockpath = join(root, '.hg/inotify.sock')
-
-        self.realsockpath = self.sockpath
-        if os.path.islink(self.sockpath):
-            if os.path.exists(self.sockpath):
-                self.realsockpath = os.readlink(self.sockpath)
-            else:
-                os.unlink(self.sockpath)
         try:
-            self.sock.bind(self.realsockpath)
-        except socket.error, err:
-            if err.args[0] == errno.EADDRINUSE:
-                raise AlreadyStartedException(_('cannot start: socket is '
-                                                'already bound'))
-            if err.args[0] == "AF_UNIX path too long":
-                tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
-                self.realsockpath = os.path.join(tempdir, "inotify.sock")
-                try:
-                    self.sock.bind(self.realsockpath)
-                    os.symlink(self.realsockpath, self.sockpath)
-                except (OSError, socket.error), inst:
-                    try:
-                        os.unlink(self.realsockpath)
-                    except OSError:
-                        pass
-                    os.rmdir(tempdir)
-                    if inst.errno == errno.EEXIST:
-                        raise AlreadyStartedException(_('cannot start: tried '
-                            'linking .hg/inotify.sock to a temporary socket but'
-                            ' .hg/inotify.sock already exists'))
-                    raise
-            else:
-                raise
-        self.sock.listen(5)
+            self.sock = posix.unixdomainserver(
+                lambda p: os.path.join(root, '.hg', p),
+                'inotify')
+        except (OSError, socket.error), err:
+            if err.errno == errno.EADDRINUSE:
+                raise AlreadyStartedException(_('cannot start: '
+                                                'socket is already bound'))
+            raise
         self.fileno = self.sock.fileno
 
     def answer_stat_query(self, cs):
--- a/mercurial/posix.py	Tue Dec 18 17:33:32 2012 -0800
+++ b/mercurial/posix.py	Tue Dec 18 17:15:13 2012 -0800
@@ -7,7 +7,7 @@
 
 from i18n import _
 import encoding
-import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
+import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
 
 posixfile = open
 normpath = os.path.normpath
@@ -483,3 +483,43 @@
 
 def executablepath():
     return None # available on Windows only
+
+class unixdomainserver(socket.socket):
+    def __init__(self, join, subsystem):
+        '''Create a unix domain socket with the given prefix.'''
+        super(unixdomainserver, self).__init__(socket.AF_UNIX)
+        sockname = subsystem + '.sock'
+        self.realpath = self.path = join(sockname)
+        if os.path.islink(self.path):
+            if os.path.exists(self.path):
+                self.realpath = os.readlink(self.path)
+            else:
+                os.unlink(self.path)
+        try:
+            self.bind(self.realpath)
+        except socket.error, err:
+            if err.args[0] == 'AF_UNIX path too long':
+                tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
+                self.realpath = os.path.join(tmpdir, sockname)
+                try:
+                    self.bind(self.realpath)
+                    os.symlink(self.realpath, self.path)
+                except (OSError, socket.error):
+                    self.cleanup()
+                    raise
+            else:
+                raise
+        self.listen(5)
+
+    def cleanup(self):
+        def okayifmissing(f, path):
+            try:
+                f(path)
+            except OSError, err:
+                if err.errno != errno.ENOENT:
+                    raise
+
+        okayifmissing(os.unlink, self.path)
+        if self.realpath != self.path:
+            okayifmissing(os.unlink, self.realpath)
+            okayifmissing(os.rmdir, os.path.dirname(self.realpath))