Commit ca182eef by Julien Muchembled

Remove most dangerous uses of 'chdir' syscall

Because chdir/getcwd is global to the whole process, it is not thread-safe
and may cause very serious bugs like data loss (for example when 'os.remove'
or 'shutil.rmtree' are called with relative paths).

There still remain uses of 'chdir' in ERP5 Subversion. A temporary quick change
is done to reduce the probability of race conditions, but this should really be
fixed.
1 parent 45e56b46
......@@ -1619,25 +1619,23 @@ class ERP5Generator(PortalGenerator):
@classmethod
def bootstrap(cls, context, bt_name, item_name, content_id_list):
cwd = os.getcwd()
try:
os.chdir(getBootstrapBusinessTemplateUrl(bt_name))
from Products.ERP5.Document.BusinessTemplate import quote
traverse = context.unrestrictedTraverse
for root, dirs, files in os.walk(os.path.join(item_name, context.id)):
container_path = root.split(os.sep)[2:]
load = traverse(container_path)._importObjectFromFile
if container_path:
id_set = set(x[:-4] for x in files if x[-4:] == '.xml')
else:
id_set = set(quote(x) for x in content_id_list
if not context.hasObject(x))
dirs[:] = id_set.intersection(dirs)
for file in id_set:
load(os.path.join(root, file + '.xml'),
verify=False, set_owner=False, suppress_events=True)
finally:
os.chdir(cwd)
bt_path = getBootstrapBusinessTemplateUrl(bt_name)
from Products.ERP5.Document.BusinessTemplate import quote
traverse = context.unrestrictedTraverse
top = os.path.join(bt_path, item_name, context.id)
prefix_len = len(os.path.join(top, ''))
for root, dirs, files in os.walk(top):
container_path = root[prefix_len:]
load = traverse(container_path)._importObjectFromFile
if container_path:
id_set = set(x[:-4] for x in files if x[-4:] == '.xml')
else:
id_set = set(quote(x) for x in content_id_list
if not context.hasObject(x))
dirs[:] = id_set.intersection(dirs)
for file in id_set:
load(os.path.join(root, file + '.xml'),
verify=False, set_owner=False, suppress_events=True)
def setupLastTools(self, p, **kw):
"""
......
......@@ -256,27 +256,15 @@ class TemplateTool (BaseTool):
Export the Business Template as a bt5 file and offer the user to
download it.
"""
path = business_template.getTitle()
path = pathname2url(path)
# XXX Why is it necessary to create a temporary directory?
tmpdir_path = mkdtemp()
# XXX not thread safe
current_directory = os.getcwd()
os.chdir(tmpdir_path)
absolute_path = os.path.abspath(path)
export_string = business_template.export(path=absolute_path)
os.chdir(current_directory)
if RESPONSE is not None:
RESPONSE.setHeader('Content-type','tar/x-gzip')
RESPONSE.setHeader('Content-Disposition',
'inline;filename=%s-%s.bt5' % \
(path,
business_template.getVersion()))
export_string = business_template.export()
try:
if RESPONSE is not None:
RESPONSE.setHeader('Content-type','tar/x-gzip')
RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s-%s.bt5'
% (business_template.getTitle(), business_template.getVersion()))
return export_string.getvalue()
finally:
export_string.close()
shutil.rmtree(tmpdir_path)
security.declareProtected( 'Import/Export objects', 'publish' )
def publish(self, business_template, url, username=None, password=None):
......
......@@ -30,15 +30,35 @@
##############################################################################
import errno, glob, os, re, shutil
import threading
from ZTUtils import make_query
from Products.ERP5Type.Message import translateString
from Products.ERP5Type.Utils import simple_decorator
from Products.ERP5.Document.BusinessTemplate import BusinessTemplateFolder
from Products.ERP5VCS.WorkingCopy import \
WorkingCopy, Dir, File, chdir_working_copy, selfcached, \
WorkingCopy, Dir, File, selfcached, \
NotAWorkingCopyError, NotVersionedError, VcsConflictError
from Products.ERP5VCS.SubversionClient import \
newSubversionClient, SubversionLoginError, SubversionSSLTrustError
# XXX Still not thread safe !!! Proper fix is to never use 'os.chdir'
# Using a RLock is a temporary quick change that only protects against
# concurrent uses of ERP5 Subversion.
_chdir_lock = threading.RLock()
@simple_decorator
def chdir_working_copy(func):
def decorator(self, *args, **kw):
_chdir_lock.acquire()
try:
cwd = os.getcwd()
try:
os.chdir(self.working_copy)
return func(self, *args, **kw)
finally:
os.chdir(cwd)
finally:
_chdir_lock.release()
return decorator
class Subversion(WorkingCopy):
......@@ -238,6 +258,7 @@ class Subversion(WorkingCopy):
'Files committed successfully in revision ${revision}',
mapping=dict(revision=getRevisionNumber(revision))))))
@chdir_working_copy
def _export(self, business_template):
bta = BusinessTemplateWorkingCopy(creation=1, client=self._getClient())
bta.export(business_template)
......
......@@ -43,17 +43,6 @@ from Products.ERP5.Document.BusinessTemplate import BusinessTemplateFolder
from Products.ERP5Type.Utils import simple_decorator
@simple_decorator
def chdir_working_copy(func):
def decorator(self, *args, **kw):
cwd = os.getcwd()
try:
os.chdir(self.working_copy)
return func(self, *args, **kw)
finally:
os.chdir(cwd)
return decorator
@simple_decorator
def selfcached(func):
"""Return a function which stores a computed value in an instance
at the first call.
......@@ -202,15 +191,10 @@ class WorkingCopy(Implicit):
business_template.build()
# XXX: Big hack to make export work as expected.
transaction.commit()
old_cwd = os.getcwd()
try:
os.chdir(self.working_copy)
self._export(business_template)
finally:
os.chdir(old_cwd)
self._export(business_template)
def _export(self, business_template):
bta = BusinessTemplateWorkingCopy(creation=1)
bta = BusinessTemplateWorkingCopy(creation=1, path=self.working_copy)
self.addremove(*bta.export(business_template))
def showOld(self, path):
......@@ -386,6 +370,7 @@ class BusinessTemplateWorkingCopy(BusinessTemplateFolder):
def _writeString(self, obj, path):
self.file_set.add(path)
self._makeParent(path)
path = os.path.join(self.path, path)
# write file unless unchanged
file = None
try:
......@@ -412,8 +397,9 @@ class BusinessTemplateWorkingCopy(BusinessTemplateFolder):
path = os.path.dirname(path)
if path and path not in self.dir_set:
self._makeParent(path)
if not os.path.exists(path):
os.mkdir(path)
real_path = os.path.join(self.path, path)
if not os.path.exists(real_path):
os.mkdir(real_path)
self.dir_set.add(path)
def export(self, business_template):
......@@ -423,8 +409,8 @@ class BusinessTemplateWorkingCopy(BusinessTemplateFolder):
business_template.export(bta=self)
# Remove dangling files/dirs
removed_set = set()
prefix_length = len(os.path.join('.', ''))
for dirpath, dirnames, filenames in os.walk('.'):
prefix_length = len(os.path.join(self.path, ''))
for dirpath, dirnames, filenames in os.walk(self.path):
dirpath = dirpath[prefix_length:]
for i in xrange(len(dirnames) - 1, -1, -1):
d = dirnames[i]
......@@ -432,13 +418,13 @@ class BusinessTemplateWorkingCopy(BusinessTemplateFolder):
d = os.path.join(dirpath, d)
if d in self.dir_set:
continue
shutil.rmtree(d)
shutil.rmtree(os.path.join(self.path, d))
removed_set.add(d)
del dirnames[i]
for f in filenames:
f = os.path.join(dirpath, f)
if f not in self.file_set:
os.remove(f)
os.remove(os.path.join(self.path, f))
removed_set.add(f)
return self.file_set, removed_set
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!