bpo-20443: _PyConfig_Read() gets the absolute path of run_filename (G… · python/cpython@3939c32 · GitHub
Skip to content

Commit 3939c32

Browse files
authored
bpo-20443: _PyConfig_Read() gets the absolute path of run_filename (GH-14053)
Python now gets the absolute path of the script filename specified on the command line (ex: "python3 script.py"): the __file__ attribute of the __main__ module, sys.argv[0] and sys.path[0] become an absolute path, rather than a relative path. * Add _Py_isabs() and _Py_abspath() functions. * _PyConfig_Read() now tries to get the absolute path of run_filename, but keeps the relative path if _Py_abspath() fails. * Reimplement os._getfullpathname() using _Py_abspath(). * Use _Py_isabs() in getpath.c.
1 parent 080b6b4 commit 3939c32

10 files changed

Lines changed: 211 additions & 42 deletions

File tree

Doc/whatsnew/3.9.rst

Lines changed: 8 additions & 0 deletions

Include/fileutils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ PyAPI_FUNC(wchar_t*) _Py_wrealpath(
154154
size_t resolved_path_len);
155155
#endif
156156

157+
#ifndef MS_WINDOWS
158+
PyAPI_FUNC(int) _Py_isabs(const wchar_t *path);
159+
#endif
160+
161+
PyAPI_FUNC(int) _Py_abspath(const wchar_t *path, wchar_t **abspath_p);
162+
157163
PyAPI_FUNC(wchar_t*) _Py_wgetcwd(
158164
wchar_t *buf,
159165
/* Number of characters of 'buf' buffer

Lib/test/test_cmd_line_script.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,18 @@ def test_basic_script(self):
217217
with support.temp_dir() as script_dir:
218218
script_name = _make_test_script(script_dir, 'script')
219219
self._check_script(script_name, script_name, script_name,
220+
script_dir, None,
221+
importlib.machinery.SourceFileLoader,
222+
expected_cwd=script_dir)
223+
224+
def test_script_abspath(self):
225+
# pass the script using the relative path, expect the absolute path
226+
# in __file__ and sys.argv[0]
227+
with support.temp_cwd() as script_dir:
228+
self.assertTrue(os.path.isabs(script_dir), script_dir)
229+
230+
script_name = _make_test_script(script_dir, 'script')
231+
self._check_script(os.path.basename(script_name), script_name, script_name,
220232
script_dir, None,
221233
importlib.machinery.SourceFileLoader)
222234

@@ -542,7 +554,7 @@ def test_non_ascii(self):
542554

543555
# Issue #16218
544556
source = 'print(ascii(__file__))\n'
545-
script_name = _make_test_script(os.curdir, name, source)
557+
script_name = _make_test_script(os.getcwd(), name, source)
546558
self.addCleanup(support.unlink, script_name)
547559
rc, stdout, stderr = assert_python_ok(script_name)
548560
self.assertEqual(

Lib/test/test_embed.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,10 @@ def test_preinit_parse_argv(self):
805805
preconfig = {
806806
'allocator': PYMEM_ALLOCATOR_DEBUG,
807807
}
808+
script_abspath = os.path.abspath('script.py')
808809
config = {
809-
'argv': ['script.py'],
810-
'run_filename': 'script.py',
810+
'argv': [script_abspath],
811+
'run_filename': script_abspath,
811812
'dev_mode': 1,
812813
'faulthandler': 1,
813814
'warnoptions': ['default'],

Lib/test/test_warnings/__init__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -926,27 +926,26 @@ def run(*args):
926926
return stderr
927927

928928
# tracemalloc disabled
929+
filename = os.path.abspath(support.TESTFN)
929930
stderr = run('-Wd', support.TESTFN)
930-
expected = textwrap.dedent('''
931-
{fname}:5: ResourceWarning: unclosed file <...>
931+
expected = textwrap.dedent(f'''
932+
{filename}:5: ResourceWarning: unclosed file <...>
932933
f = None
933934
ResourceWarning: Enable tracemalloc to get the object allocation traceback
934-
''')
935-
expected = expected.format(fname=support.TESTFN).strip()
935+
''').strip()
936936
self.assertEqual(stderr, expected)
937937

938938
# tracemalloc enabled
939939
stderr = run('-Wd', '-X', 'tracemalloc=2', support.TESTFN)
940-
expected = textwrap.dedent('''
941-
{fname}:5: ResourceWarning: unclosed file <...>
940+
expected = textwrap.dedent(f'''
941+
{filename}:5: ResourceWarning: unclosed file <...>
942942
f = None
943943
Object allocated at (most recent call last):
944-
File "{fname}", lineno 7
944+
File "{filename}", lineno 7
945945
func()
946-
File "{fname}", lineno 3
946+
File "{filename}", lineno 3
947947
f = open(__file__)
948-
''')
949-
expected = expected.format(fname=support.TESTFN).strip()
948+
''').strip()
950949
self.assertEqual(stderr, expected)
951950

952951

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Python now gets the absolute path of the script filename specified on the
2+
command line (ex: "python3 script.py"): the __file__ attribute of the __main__
3+
module and sys.path[0] become an absolute path, rather than a relative path.

Modules/getpath.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ static PyStatus
240240
joinpath(wchar_t *buffer, const wchar_t *stuff, size_t buflen)
241241
{
242242
size_t n, k;
243-
if (stuff[0] != SEP) {
243+
if (!_Py_isabs(stuff)) {
244244
n = wcslen(buffer);
245245
if (n >= buflen) {
246246
return PATHLEN_ERR();
@@ -283,7 +283,7 @@ safe_wcscpy(wchar_t *dst, const wchar_t *src, size_t n)
283283
static PyStatus
284284
copy_absolute(wchar_t *path, const wchar_t *p, size_t pathlen)
285285
{
286-
if (p[0] == SEP) {
286+
if (_Py_isabs(p)) {
287287
if (safe_wcscpy(path, p, pathlen) < 0) {
288288
return PATHLEN_ERR();
289289
}
@@ -312,7 +312,7 @@ copy_absolute(wchar_t *path, const wchar_t *p, size_t pathlen)
312312
static PyStatus
313313
absolutize(wchar_t *path, size_t path_len)
314314
{
315-
if (path[0] == SEP) {
315+
if (_Py_isabs(path)) {
316316
return _PyStatus_OK();
317317
}
318318

@@ -761,7 +761,7 @@ calculate_program_full_path(const PyConfig *config,
761761
* absolutize() should help us out below
762762
*/
763763
else if(0 == _NSGetExecutablePath(execpath, &nsexeclength) &&
764-
execpath[0] == SEP)
764+
_Py_isabs(execpath))
765765
{
766766
size_t len;
767767
wchar_t *path = Py_DecodeLocale(execpath, &len);
@@ -815,7 +815,7 @@ calculate_program_full_path(const PyConfig *config,
815815
else {
816816
program_full_path[0] = '\0';
817817
}
818-
if (program_full_path[0] != SEP && program_full_path[0] != '\0') {
818+
if (!_Py_isabs(program_full_path) && program_full_path[0] != '\0') {
819819
status = absolutize(program_full_path, program_full_path_len);
820820
if (_PyStatus_EXCEPTION(status)) {
821821
return status;
@@ -916,7 +916,7 @@ calculate_argv0_path(PyCalculatePath *calculate, const wchar_t *program_full_pat
916916
const size_t buflen = Py_ARRAY_LENGTH(tmpbuffer);
917917
int linklen = _Py_wreadlink(program_full_path, tmpbuffer, buflen);
918918
while (linklen != -1) {
919-
if (tmpbuffer[0] == SEP) {
919+
if (_Py_isabs(tmpbuffer)) {
920920
/* tmpbuffer should never be longer than MAXPATHLEN,
921921
but extra check does not hurt */
922922
if (safe_wcscpy(calculate->argv0_path, tmpbuffer, argv0_path_len) < 0) {
@@ -1046,7 +1046,7 @@ calculate_module_search_path(const PyConfig *config,
10461046
while (1) {
10471047
wchar_t *delim = wcschr(defpath, DELIM);
10481048

1049-
if (defpath[0] != SEP) {
1049+
if (!_Py_isabs(defpath)) {
10501050
/* Paths are relative to prefix */
10511051
bufsz += prefixsz;
10521052
}
@@ -1088,7 +1088,7 @@ calculate_module_search_path(const PyConfig *config,
10881088
while (1) {
10891089
wchar_t *delim = wcschr(defpath, DELIM);
10901090

1091-
if (defpath[0] != SEP) {
1091+
if (!_Py_isabs(defpath)) {
10921092
wcscat(buf, prefix);
10931093
if (prefixsz >= 2 && prefix[prefixsz - 2] != SEP &&
10941094
defpath[0] != (delim ? DELIM : L'\0'))

Modules/posixmodule.c

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3784,29 +3784,25 @@ static PyObject *
37843784
os__getfullpathname_impl(PyObject *module, path_t *path)
37853785
/*[clinic end generated code: output=bb8679d56845bc9b input=332ed537c29d0a3e]*/
37863786
{
3787-
wchar_t woutbuf[MAX_PATH], *woutbufp = woutbuf;
3788-
wchar_t *wtemp;
3789-
DWORD result;
3790-
PyObject *v;
3787+
wchar_t *abspath;
37913788

3792-
result = GetFullPathNameW(path->wide,
3793-
Py_ARRAY_LENGTH(woutbuf),
3794-
woutbuf, &wtemp);
3795-
if (result > Py_ARRAY_LENGTH(woutbuf)) {
3796-
woutbufp = PyMem_New(wchar_t, result);
3797-
if (!woutbufp)
3798-
return PyErr_NoMemory();
3799-
result = GetFullPathNameW(path->wide, result, woutbufp, &wtemp);
3789+
/* _Py_abspath() is implemented with GetFullPathNameW() on Windows */
3790+
if (_Py_abspath(path->wide, &abspath) < 0) {
3791+
return win32_error_object("GetFullPathNameW", path->object);
38003792
}
3801-
if (result) {
3802-
v = PyUnicode_FromWideChar(woutbufp, wcslen(woutbufp));
3803-
if (path->narrow)
3804-
Py_SETREF(v, PyUnicode_EncodeFSDefault(v));
3805-
} else
3806-
v = win32_error_object("GetFullPathNameW", path->object);
3807-
if (woutbufp != woutbuf)
3808-
PyMem_Free(woutbufp);
3809-
return v;
3793+
if (abspath == NULL) {
3794+
return PyErr_NoMemory();
3795+
}
3796+
3797+
PyObject *str = PyUnicode_FromWideChar(abspath, wcslen(abspath));
3798+
PyMem_RawFree(abspath);
3799+
if (str == NULL) {
3800+
return NULL;
3801+
}
3802+
if (path->narrow) {
3803+
Py_SETREF(str, PyUnicode_EncodeFSDefault(str));
3804+
}
3805+
return str;
38103806
}
38113807

38123808

Python/fileutils.c

Lines changed: 97 additions & 0 deletions

0 commit comments

Comments
 (0)