6
6
from __future__ import absolute_import
7
7
from __future__ import division
8
8
9
+ import atexit
9
10
import collections
11
+ import errno
10
12
import functools
11
13
import logging
12
14
import os
15
+ import os .path
13
16
import platform
17
+ import shutil
14
18
import six
15
19
import socket
16
- import stat
17
20
import string
18
- import subprocess
19
21
import sys
22
+ import tempfile
20
23
import threading
21
24
import time
22
25
@@ -345,6 +348,10 @@ class ContextType(object):
345
348
'binary' : None ,
346
349
'bits' : 32 ,
347
350
'buffer_size' : 4096 ,
351
+ 'cache_dir_base' : os .environ .get (
352
+ 'XDG_CACHE_HOME' ,
353
+ os .path .join (os .path .expanduser ('~' ), '.cache' )
354
+ ),
348
355
'cyclic_alphabet' : string .ascii_lowercase .encode (),
349
356
'cyclic_size' : 4 ,
350
357
'delete_corefiles' : False ,
@@ -1269,6 +1276,21 @@ def buffer_size(self, size):
1269
1276
"""
1270
1277
return int (size )
1271
1278
1279
+ @_validator
1280
+ def cache_dir_base (self , new_base ):
1281
+ """Base directory to use for caching content.
1282
+
1283
+ Changing this to a different value will clear the `cache_dir` path
1284
+ stored in TLS since a new path will need to be generated to respect the
1285
+ new `cache_dir_base` value.
1286
+ """
1287
+
1288
+ if new_base != self .cache_dir_base :
1289
+ del self ._tls ["cache_dir" ]
1290
+ if os .access (new_base , os .F_OK ) and not os .access (new_base , os .W_OK ):
1291
+ raise OSError (errno .EPERM , "Cache base dir is not writable" )
1292
+ return new_base
1293
+
1272
1294
@property
1273
1295
def cache_dir (self ):
1274
1296
"""Directory used for caching data.
@@ -1282,32 +1304,59 @@ def cache_dir(self):
1282
1304
>>> cache_dir is not None
1283
1305
True
1284
1306
>>> os.chmod(cache_dir, 0o000)
1307
+ >>> del context._tls['cache_dir']
1285
1308
>>> context.cache_dir is None
1286
1309
True
1287
1310
>>> os.chmod(cache_dir, 0o755)
1288
1311
>>> cache_dir == context.cache_dir
1289
1312
True
1290
1313
"""
1291
- xdg_cache_home = os .environ .get ('XDG_CACHE_HOME' ) or \
1292
- os .path .join (os .path .expanduser ('~' ), '.cache' )
1293
-
1294
- if not os .access (xdg_cache_home , os .W_OK ):
1295
- return None
1296
-
1297
- cache = os .path .join (xdg_cache_home , '.pwntools-cache-%d.%d' % sys .version_info [:2 ])
1298
-
1299
- if not os .path .exists (cache ):
1300
- try :
1301
- os .mkdir (cache )
1302
- except OSError :
1303
- return None
1314
+ try :
1315
+ # If the TLS already has a cache directory path, we return it
1316
+ # without any futher checks since it must have been valid when it
1317
+ # was set and if that has changed, hiding the TOCTOU here would be
1318
+ # potentially confusing
1319
+ return self ._tls ["cache_dir" ]
1320
+ except KeyError :
1321
+ pass
1304
1322
1305
- # Some wargames e.g. pwnable.kr have created dummy directories
1306
- # which cannot be modified by the user account (owned by root).
1307
- if not os .access (cache , os .W_OK ):
1323
+ # Attempt to create a Python version specific cache dir and its parents
1324
+ cache_dirname = '.pwntools-cache-%d.%d' % sys .version_info [:2 ]
1325
+ cache_dirpath = os .path .join (self .cache_dir_base , cache_dirname )
1326
+ try :
1327
+ os .makedirs (cache_dirpath )
1328
+ except OSError as exc :
1329
+ # If we failed for any reason other than the cache directory
1330
+ # already existing then we'll fall back to a temporary directory
1331
+ # object which doesn't respect the `cache_dir_base`
1332
+ if exc .errno != errno .EEXIST :
1333
+ try :
1334
+ cache_dirpath = tempfile .mkdtemp (prefix = ".pwntools-tmp" )
1335
+ except IOError :
1336
+ # This implies no good candidates for temporary files so we
1337
+ # have to return `None`
1338
+ return None
1339
+ else :
1340
+ # Ensure the temporary cache dir is cleaned up on exit. A
1341
+ # `TemporaryDirectory` would do this better upon garbage
1342
+ # collection but this is necessary for Python 2 support.
1343
+ atexit .register (shutil .rmtree , cache_dirpath )
1344
+ # By this time we have a cache directory which exists but we don't know
1345
+ # if it is actually writable. Some wargames e.g. pwnable.kr have
1346
+ # created dummy directories which cannot be modified by the user
1347
+ # account (owned by root).
1348
+ if os .access (cache_dirpath , os .W_OK ):
1349
+ # Stash this in TLS for later reuse
1350
+ self ._tls ["cache_dir" ] = cache_dirpath
1351
+ return cache_dirpath
1352
+ else :
1308
1353
return None
1309
1354
1310
- return cache
1355
+ @cache_dir .setter
1356
+ def cache_dir (self , v ):
1357
+ if os .access (v , os .W_OK ):
1358
+ # Stash this in TLS for later reuse
1359
+ self ._tls ["cache_dir" ] = v
1311
1360
1312
1361
@_validator
1313
1362
def delete_corefiles (self , v ):
0 commit comments