##// END OF EJS Templates
fix race condition in profiledir creation.
James Porter -
Show More
@@ -1,274 +1,281 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An object for managing IPython profile directories.
3 An object for managing IPython profile directories.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2011 The IPython Development Team
14 # Copyright (C) 2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import os
24 import os
25 import shutil
25 import shutil
26 import errno
26 import errno
27 import time
27
28
28 from IPython.config.configurable import LoggingConfigurable
29 from IPython.config.configurable import LoggingConfigurable
29 from IPython.utils.path import get_ipython_package_dir, expand_path
30 from IPython.utils.path import get_ipython_package_dir, expand_path
30 from IPython.utils import py3compat
31 from IPython.utils import py3compat
31 from IPython.utils.traitlets import Unicode, Bool
32 from IPython.utils.traitlets import Unicode, Bool
32
33
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34 # Classes and functions
35 # Classes and functions
35 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
36
37
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Module errors
40 # Module errors
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42 class ProfileDirError(Exception):
43 class ProfileDirError(Exception):
43 pass
44 pass
44
45
45
46
46 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
47 # Class for managing profile directories
48 # Class for managing profile directories
48 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
49
50
50 class ProfileDir(LoggingConfigurable):
51 class ProfileDir(LoggingConfigurable):
51 """An object to manage the profile directory and its resources.
52 """An object to manage the profile directory and its resources.
52
53
53 The profile directory is used by all IPython applications, to manage
54 The profile directory is used by all IPython applications, to manage
54 configuration, logging and security.
55 configuration, logging and security.
55
56
56 This object knows how to find, create and manage these directories. This
57 This object knows how to find, create and manage these directories. This
57 should be used by any code that wants to handle profiles.
58 should be used by any code that wants to handle profiles.
58 """
59 """
59
60
60 security_dir_name = Unicode('security')
61 security_dir_name = Unicode('security')
61 log_dir_name = Unicode('log')
62 log_dir_name = Unicode('log')
62 startup_dir_name = Unicode('startup')
63 startup_dir_name = Unicode('startup')
63 pid_dir_name = Unicode('pid')
64 pid_dir_name = Unicode('pid')
64 static_dir_name = Unicode('static')
65 static_dir_name = Unicode('static')
65 security_dir = Unicode(u'')
66 security_dir = Unicode(u'')
66 log_dir = Unicode(u'')
67 log_dir = Unicode(u'')
67 startup_dir = Unicode(u'')
68 startup_dir = Unicode(u'')
68 pid_dir = Unicode(u'')
69 pid_dir = Unicode(u'')
69 static_dir = Unicode(u'')
70 static_dir = Unicode(u'')
70
71
71 location = Unicode(u'', config=True,
72 location = Unicode(u'', config=True,
72 help="""Set the profile location directly. This overrides the logic used by the
73 help="""Set the profile location directly. This overrides the logic used by the
73 `profile` option.""",
74 `profile` option.""",
74 )
75 )
75
76
76 _location_isset = Bool(False) # flag for detecting multiply set location
77 _location_isset = Bool(False) # flag for detecting multiply set location
77
78
78 def _location_changed(self, name, old, new):
79 def _location_changed(self, name, old, new):
79 if self._location_isset:
80 if self._location_isset:
80 raise RuntimeError("Cannot set profile location more than once.")
81 raise RuntimeError("Cannot set profile location more than once.")
81 self._location_isset = True
82 self._location_isset = True
82 if not os.path.isdir(new):
83 num_tries = 0
84 max_tries = 5
85 while not os.path.isdir(new):
86 try:
83 os.makedirs(new)
87 os.makedirs(new)
88 except OSError:
89 if num_tries > max_tries:
90 raise
91 num_tries += 1
92 time.sleep(0.5)
84
93
85 # ensure config files exist:
94 # ensure config files exist:
86 self.security_dir = os.path.join(new, self.security_dir_name)
95 self.security_dir = os.path.join(new, self.security_dir_name)
87 self.log_dir = os.path.join(new, self.log_dir_name)
96 self.log_dir = os.path.join(new, self.log_dir_name)
88 self.startup_dir = os.path.join(new, self.startup_dir_name)
97 self.startup_dir = os.path.join(new, self.startup_dir_name)
89 self.pid_dir = os.path.join(new, self.pid_dir_name)
98 self.pid_dir = os.path.join(new, self.pid_dir_name)
90 self.static_dir = os.path.join(new, self.static_dir_name)
99 self.static_dir = os.path.join(new, self.static_dir_name)
91 self.check_dirs()
100 self.check_dirs()
92
101
93 def _log_dir_changed(self, name, old, new):
102 def _log_dir_changed(self, name, old, new):
94 self.check_log_dir()
103 self.check_log_dir()
95
104
96 def _mkdir(self, path, mode=None):
105 def _mkdir(self, path, mode=None):
97 """ensure a directory exists at a given path
106 """ensure a directory exists at a given path
98
107
99 This is a version of os.mkdir, with the following differences:
108 This is a version of os.mkdir, with the following differences:
100
109
101 - returns True if it created the directory, False otherwise
110 - returns True if it created the directory, False otherwise
102 - ignores EEXIST, protecting against race conditions where
111 - ignores EEXIST, protecting against race conditions where
103 the dir may have been created in between the check and
112 the dir may have been created in between the check and
104 the creation
113 the creation
105 - sets permissions if requested and the dir already exists
114 - sets permissions if requested and the dir already exists
106 """
115 """
107 if os.path.exists(path):
116 if os.path.exists(path):
108 if mode and os.stat(path).st_mode != mode:
117 if mode and os.stat(path).st_mode != mode:
109 try:
118 try:
110 os.chmod(path, mode)
119 os.chmod(path, mode)
111 except OSError:
120 except OSError:
112 self.log.warn(
121 self.log.warn(
113 "Could not set permissions on %s",
122 "Could not set permissions on %s",
114 path
123 path
115 )
124 )
116 return False
125 return False
117 try:
126 try:
118 if mode:
127 if mode:
119 os.mkdir(path, mode)
128 os.mkdir(path, mode)
120 else:
129 else:
121 os.mkdir(path)
130 os.mkdir(path)
122 except OSError as e:
131 except OSError as e:
123 if e.errno == errno.EEXIST:
132 if e.errno == errno.EEXIST:
124 return False
133 return False
125 else:
134 else:
126 raise
135 raise
127
136
128 return True
137 return True
129
138
130 def check_log_dir(self):
139 def check_log_dir(self):
131 self._mkdir(self.log_dir)
140 self._mkdir(self.log_dir)
132
141
133 def _startup_dir_changed(self, name, old, new):
142 def _startup_dir_changed(self, name, old, new):
134 self.check_startup_dir()
143 self.check_startup_dir()
135
144
136 def check_startup_dir(self):
145 def check_startup_dir(self):
137 self._mkdir(self.startup_dir)
146 self._mkdir(self.startup_dir)
138
147
139 readme = os.path.join(self.startup_dir, 'README')
148 readme = os.path.join(self.startup_dir, 'README')
140 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
149 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
141
150
142 if not os.path.exists(src):
151 if not os.path.exists(src):
143 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
152 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
144
153
145 if os.path.exists(src) and not os.path.exists(readme):
154 if os.path.exists(src) and not os.path.exists(readme):
146 shutil.copy(src, readme)
155 shutil.copy(src, readme)
147
156
148 def _security_dir_changed(self, name, old, new):
157 def _security_dir_changed(self, name, old, new):
149 self.check_security_dir()
158 self.check_security_dir()
150
159
151 def check_security_dir(self):
160 def check_security_dir(self):
152 self._mkdir(self.security_dir, 0o40700)
161 self._mkdir(self.security_dir, 0o40700)
153
162
154 def _pid_dir_changed(self, name, old, new):
163 def _pid_dir_changed(self, name, old, new):
155 self.check_pid_dir()
164 self.check_pid_dir()
156
165
157 def check_pid_dir(self):
166 def check_pid_dir(self):
158 self._mkdir(self.pid_dir, 0o40700)
167 self._mkdir(self.pid_dir, 0o40700)
159
168
160 def _static_dir_changed(self, name, old, new):
169 def _static_dir_changed(self, name, old, new):
161 self.check_startup_dir()
170 self.check_startup_dir()
162
171
163 def check_static_dir(self):
172 def check_static_dir(self):
164 self._mkdir(self.static_dir)
173 self._mkdir(self.static_dir)
165 custom = os.path.join(self.static_dir, 'custom')
174 custom = os.path.join(self.static_dir, 'custom')
166 self._mkdir(custom)
175 self._mkdir(custom)
167 from IPython.html import DEFAULT_STATIC_FILES_PATH
176 from IPython.html import DEFAULT_STATIC_FILES_PATH
168 for fname in ('custom.js', 'custom.css'):
177 for fname in ('custom.js', 'custom.css'):
169 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
178 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
170 dest = os.path.join(custom, fname)
179 dest = os.path.join(custom, fname)
171 if not os.path.exists(src):
180 if not os.path.exists(src):
172 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
181 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
173 continue
182 continue
174 if not os.path.exists(dest):
183 if not os.path.exists(dest):
175 shutil.copy(src, dest)
184 shutil.copy(src, dest)
176
185
177 def check_dirs(self):
186 def check_dirs(self):
178 self.check_security_dir()
187 self.check_security_dir()
179 self.check_log_dir()
188 self.check_log_dir()
180 self.check_pid_dir()
189 self.check_pid_dir()
181 self.check_startup_dir()
190 self.check_startup_dir()
182 self.check_static_dir()
191 self.check_static_dir()
183
192
184 def copy_config_file(self, config_file, path=None, overwrite=False):
193 def copy_config_file(self, config_file, path=None, overwrite=False):
185 """Copy a default config file into the active profile directory.
194 """Copy a default config file into the active profile directory.
186
195
187 Default configuration files are kept in :mod:`IPython.config.default`.
196 Default configuration files are kept in :mod:`IPython.config.default`.
188 This function moves these from that location to the working profile
197 This function moves these from that location to the working profile
189 directory.
198 directory.
190 """
199 """
191 dst = os.path.join(self.location, config_file)
200 dst = os.path.join(self.location, config_file)
192 if os.path.isfile(dst) and not overwrite:
201 if os.path.isfile(dst) and not overwrite:
193 return False
202 return False
194 if path is None:
203 if path is None:
195 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
204 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
196 src = os.path.join(path, config_file)
205 src = os.path.join(path, config_file)
197 shutil.copy(src, dst)
206 shutil.copy(src, dst)
198 return True
207 return True
199
208
200 @classmethod
209 @classmethod
201 def create_profile_dir(cls, profile_dir, config=None):
210 def create_profile_dir(cls, profile_dir, config=None):
202 """Create a new profile directory given a full path.
211 """Create a new profile directory given a full path.
203
212
204 Parameters
213 Parameters
205 ----------
214 ----------
206 profile_dir : str
215 profile_dir : str
207 The full path to the profile directory. If it does exist, it will
216 The full path to the profile directory. If it does exist, it will
208 be used. If not, it will be created.
217 be used. If not, it will be created.
209 """
218 """
210 return cls(location=profile_dir, config=config)
219 return cls(location=profile_dir, config=config)
211
220
212 @classmethod
221 @classmethod
213 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
222 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
214 """Create a profile dir by profile name and path.
223 """Create a profile dir by profile name and path.
215
224
216 Parameters
225 Parameters
217 ----------
226 ----------
218 path : unicode
227 path : unicode
219 The path (directory) to put the profile directory in.
228 The path (directory) to put the profile directory in.
220 name : unicode
229 name : unicode
221 The name of the profile. The name of the profile directory will
230 The name of the profile. The name of the profile directory will
222 be "profile_<profile>".
231 be "profile_<profile>".
223 """
232 """
224 if not os.path.isdir(path):
233 if not os.path.isdir(path):
225 raise ProfileDirError('Directory not found: %s' % path)
234 raise ProfileDirError('Directory not found: %s' % path)
226 profile_dir = os.path.join(path, u'profile_' + name)
235 profile_dir = os.path.join(path, u'profile_' + name)
227 return cls(location=profile_dir, config=config)
236 return cls(location=profile_dir, config=config)
228
237
229 @classmethod
238 @classmethod
230 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
239 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
231 """Find an existing profile dir by profile name, return its ProfileDir.
240 """Find an existing profile dir by profile name, return its ProfileDir.
232
241
233 This searches through a sequence of paths for a profile dir. If it
242 This searches through a sequence of paths for a profile dir. If it
234 is not found, a :class:`ProfileDirError` exception will be raised.
243 is not found, a :class:`ProfileDirError` exception will be raised.
235
244
236 The search path algorithm is:
245 The search path algorithm is:
237 1. ``py3compat.getcwd()``
246 1. ``py3compat.getcwd()``
238 2. ``ipython_dir``
247 2. ``ipython_dir``
239
248
240 Parameters
249 Parameters
241 ----------
250 ----------
242 ipython_dir : unicode or str
251 ipython_dir : unicode or str
243 The IPython directory to use.
252 The IPython directory to use.
244 name : unicode or str
253 name : unicode or str
245 The name of the profile. The name of the profile directory
254 The name of the profile. The name of the profile directory
246 will be "profile_<profile>".
255 will be "profile_<profile>".
247 """
256 """
248 dirname = u'profile_' + name
257 dirname = u'profile_' + name
249 paths = [py3compat.getcwd(), ipython_dir]
258 paths = [py3compat.getcwd(), ipython_dir]
250 for p in paths:
259 for p in paths:
251 profile_dir = os.path.join(p, dirname)
260 profile_dir = os.path.join(p, dirname)
252 if os.path.isdir(profile_dir):
261 if os.path.isdir(profile_dir):
253 return cls(location=profile_dir, config=config)
262 return cls(location=profile_dir, config=config)
254 else:
263 else:
255 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
264 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
256
265
257 @classmethod
266 @classmethod
258 def find_profile_dir(cls, profile_dir, config=None):
267 def find_profile_dir(cls, profile_dir, config=None):
259 """Find/create a profile dir and return its ProfileDir.
268 """Find/create a profile dir and return its ProfileDir.
260
269
261 This will create the profile directory if it doesn't exist.
270 This will create the profile directory if it doesn't exist.
262
271
263 Parameters
272 Parameters
264 ----------
273 ----------
265 profile_dir : unicode or str
274 profile_dir : unicode or str
266 The path of the profile directory. This is expanded using
275 The path of the profile directory. This is expanded using
267 :func:`IPython.utils.genutils.expand_path`.
276 :func:`IPython.utils.genutils.expand_path`.
268 """
277 """
269 profile_dir = expand_path(profile_dir)
278 profile_dir = expand_path(profile_dir)
270 if not os.path.isdir(profile_dir):
279 if not os.path.isdir(profile_dir):
271 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
280 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
272 return cls(location=profile_dir, config=config)
281 return cls(location=profile_dir, config=config)
273
274
General Comments 0
You need to be logged in to leave comments. Login now