/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/win32utils.py

  • Committer: Vincent Ladeuil
  • Date: 2019-11-19 18:10:28 UTC
  • mfrom: (7290.1.43 work)
  • mto: This revision was merged to the branch mainline in revision 7414.
  • Revision ID: v.ladeuil+brz@free.fr-20191119181028-rgajksejntz3vwil
MergeĀ lp:brz/3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Win32-specific helper functions
 
18
 
 
19
Only one dependency: ctypes should be installed.
 
20
"""
 
21
 
 
22
from __future__ import absolute_import
 
23
 
 
24
import glob
 
25
import os
 
26
import struct
 
27
import sys
 
28
 
 
29
from breezy import (
 
30
    cmdline,
 
31
    )
 
32
from breezy.i18n import gettext
 
33
 
 
34
has_ctypes_win32 = False
 
35
if sys.platform == 'win32':
 
36
    try:
 
37
        import ctypes
 
38
    except ImportError:
 
39
        has_ctypes_win32 = False
 
40
 
 
41
 
 
42
# Special Win32 API constants
 
43
# Handles of std streams
 
44
WIN32_STDIN_HANDLE = -10
 
45
WIN32_STDOUT_HANDLE = -11
 
46
WIN32_STDERR_HANDLE = -12
 
47
 
 
48
# CSIDL constants (from MSDN 2003)
 
49
CSIDL_APPDATA = 0x001A      # Application Data folder
 
50
# <user name>\Local Settings\Application Data (non roaming)
 
51
CSIDL_LOCAL_APPDATA = 0x001c
 
52
CSIDL_PERSONAL = 0x0005     # My Documents folder
 
53
 
 
54
# from winapi C headers
 
55
MAX_PATH = 260
 
56
UNLEN = 256
 
57
MAX_COMPUTERNAME_LENGTH = 31
 
58
 
 
59
# Registry data type ids
 
60
REG_SZ = 1
 
61
REG_EXPAND_SZ = 2
 
62
 
 
63
 
 
64
def debug_memory_win32api(message='', short=True):
 
65
    """Use trace.note() to dump the running memory info."""
 
66
    from breezy import trace
 
67
    if has_ctypes_win32:
 
68
        class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
 
69
            """Used by GetProcessMemoryInfo"""
 
70
            _fields_ = [('cb', ctypes.c_ulong),
 
71
                        ('PageFaultCount', ctypes.c_ulong),
 
72
                        ('PeakWorkingSetSize', ctypes.c_size_t),
 
73
                        ('WorkingSetSize', ctypes.c_size_t),
 
74
                        ('QuotaPeakPagedPoolUsage', ctypes.c_size_t),
 
75
                        ('QuotaPagedPoolUsage', ctypes.c_size_t),
 
76
                        ('QuotaPeakNonPagedPoolUsage', ctypes.c_size_t),
 
77
                        ('QuotaNonPagedPoolUsage', ctypes.c_size_t),
 
78
                        ('PagefileUsage', ctypes.c_size_t),
 
79
                        ('PeakPagefileUsage', ctypes.c_size_t),
 
80
                        ('PrivateUsage', ctypes.c_size_t),
 
81
                        ]
 
82
        cur_process = ctypes.windll.kernel32.GetCurrentProcess()
 
83
        mem_struct = PROCESS_MEMORY_COUNTERS_EX()
 
84
        ret = ctypes.windll.psapi.GetProcessMemoryInfo(
 
85
            cur_process, ctypes.byref(mem_struct), ctypes.sizeof(mem_struct))
 
86
        if not ret:
 
87
            trace.note(gettext('Failed to GetProcessMemoryInfo()'))
 
88
            return
 
89
        info = {'PageFaultCount': mem_struct.PageFaultCount,
 
90
                'PeakWorkingSetSize': mem_struct.PeakWorkingSetSize,
 
91
                'WorkingSetSize': mem_struct.WorkingSetSize,
 
92
                'QuotaPeakPagedPoolUsage': mem_struct.QuotaPeakPagedPoolUsage,
 
93
                'QuotaPagedPoolUsage': mem_struct.QuotaPagedPoolUsage,
 
94
                'QuotaPeakNonPagedPoolUsage':
 
95
                    mem_struct.QuotaPeakNonPagedPoolUsage,
 
96
                'QuotaNonPagedPoolUsage': mem_struct.QuotaNonPagedPoolUsage,
 
97
                'PagefileUsage': mem_struct.PagefileUsage,
 
98
                'PeakPagefileUsage': mem_struct.PeakPagefileUsage,
 
99
                'PrivateUsage': mem_struct.PrivateUsage,
 
100
                }
 
101
    else:
 
102
        trace.note(gettext('Cannot debug memory on win32 without ctypes'
 
103
                           ' or win32process'))
 
104
        return
 
105
    if short:
 
106
        # using base-2 units (see HACKING.txt).
 
107
        trace.note(gettext('WorkingSize {0:>7}KiB'
 
108
                           '\tPeakWorking {1:>7}KiB\t{2}').format(
 
109
                   info['WorkingSetSize'] / 1024,
 
110
                   info['PeakWorkingSetSize'] / 1024,
 
111
                   message))
 
112
        return
 
113
    if message:
 
114
        trace.note('%s', message)
 
115
    trace.note(gettext('WorkingSize       %8d KiB'),
 
116
               info['WorkingSetSize'] / 1024)
 
117
    trace.note(gettext('PeakWorking       %8d KiB'),
 
118
               info['PeakWorkingSetSize'] / 1024)
 
119
    trace.note(gettext('PagefileUsage     %8d KiB'),
 
120
               info.get('PagefileUsage', 0) / 1024)
 
121
    trace.note(gettext('PeakPagefileUsage %8d KiB'),
 
122
               info.get('PeakPagefileUsage', 0) / 1024)
 
123
    trace.note(gettext('PrivateUsage      %8d KiB'),
 
124
               info.get('PrivateUsage', 0) / 1024)
 
125
    trace.note(gettext('PageFaultCount    %8d'), info.get('PageFaultCount', 0))
 
126
 
 
127
 
 
128
def get_console_size(defaultx=80, defaulty=25):
 
129
    """Return size of current console.
 
130
 
 
131
    This function try to determine actual size of current working
 
132
    console window and return tuple (sizex, sizey) if success,
 
133
    or default size (defaultx, defaulty) otherwise.
 
134
    """
 
135
    if not has_ctypes_win32:
 
136
        # no ctypes is found
 
137
        return (defaultx, defaulty)
 
138
 
 
139
    # To avoid problem with redirecting output via pipe
 
140
    # we need to use stderr instead of stdout
 
141
    h = ctypes.windll.kernel32.GetStdHandle(WIN32_STDERR_HANDLE)
 
142
    csbi = ctypes.create_string_buffer(22)
 
143
    res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
 
144
 
 
145
    if res:
 
146
        (bufx, bufy, curx, cury, wattr,
 
147
         left, top, right, bottom, maxx, maxy) = struct.unpack(
 
148
            "hhhhHhhhhhh", csbi.raw)
 
149
        sizex = right - left + 1
 
150
        sizey = bottom - top + 1
 
151
        return (sizex, sizey)
 
152
    else:
 
153
        return (defaultx, defaulty)
 
154
 
 
155
 
 
156
def _get_sh_special_folder_path(csidl):
 
157
    """Call SHGetSpecialFolderPathW if available, or return None.
 
158
 
 
159
    Result is always unicode (or None).
 
160
    """
 
161
    if has_ctypes_win32:
 
162
        try:
 
163
            SHGetSpecialFolderPath = \
 
164
                ctypes.windll.shell32.SHGetSpecialFolderPathW
 
165
        except AttributeError:
 
166
            pass
 
167
        else:
 
168
            buf = ctypes.create_unicode_buffer(MAX_PATH)
 
169
            if SHGetSpecialFolderPath(None, buf, csidl, 0):
 
170
                return buf.value
 
171
    return None
 
172
 
 
173
 
 
174
def get_appdata_location():
 
175
    """Return Application Data location.
 
176
    Return None if we cannot obtain location.
 
177
 
 
178
    Windows defines two 'Application Data' folders per user - a 'roaming'
 
179
    one that moves with the user as they logon to different machines, and
 
180
    a 'local' one that stays local to the machine.  This returns the 'roaming'
 
181
    directory, and thus is suitable for storing user-preferences, etc.
 
182
    """
 
183
    appdata = _get_sh_special_folder_path(CSIDL_APPDATA)
 
184
    if appdata:
 
185
        return appdata
 
186
    # Use APPDATA if defined, will return None if not
 
187
    return get_environ_unicode('APPDATA')
 
188
 
 
189
 
 
190
def get_local_appdata_location():
 
191
    """Return Local Application Data location.
 
192
    Return the same as get_appdata_location() if we cannot obtain location.
 
193
 
 
194
    Windows defines two 'Application Data' folders per user - a 'roaming'
 
195
    one that moves with the user as they logon to different machines, and
 
196
    a 'local' one that stays local to the machine.  This returns the 'local'
 
197
    directory, and thus is suitable for caches, temp files and other things
 
198
    which don't need to move with the user.
 
199
    """
 
200
    local = _get_sh_special_folder_path(CSIDL_LOCAL_APPDATA)
 
201
    if local:
 
202
        return local
 
203
    # Vista supplies LOCALAPPDATA, but XP and earlier do not.
 
204
    local = get_environ_unicode('LOCALAPPDATA')
 
205
    if local:
 
206
        return local
 
207
    return get_appdata_location()
 
208
 
 
209
 
 
210
def get_home_location():
 
211
    """Return user's home location.
 
212
    Assume on win32 it's the <My Documents> folder.
 
213
    If location cannot be obtained return system drive root,
 
214
    i.e. C:\
 
215
    """
 
216
    home = _get_sh_special_folder_path(CSIDL_PERSONAL)
 
217
    if home:
 
218
        return home
 
219
    home = get_environ_unicode('HOME')
 
220
    if home is not None:
 
221
        return home
 
222
    homepath = get_environ_unicode('HOMEPATH')
 
223
    if homepath is not None:
 
224
        return os.path.join(get_environ_unicode('HOMEDIR', ''), home)
 
225
    # at least return windows root directory
 
226
    windir = get_environ_unicode('WINDIR')
 
227
    if windir:
 
228
        return os.path.splitdrive(windir)[0] + '/'
 
229
    # otherwise C:\ is good enough for 98% users
 
230
    return u'C:/'
 
231
 
 
232
 
 
233
def get_user_name():
 
234
    """Return user name as login name.
 
235
    If name cannot be obtained return None.
 
236
    """
 
237
    if has_ctypes_win32:
 
238
        try:
 
239
            advapi32 = ctypes.windll.advapi32
 
240
            GetUserName = getattr(advapi32, 'GetUserNameW')
 
241
        except AttributeError:
 
242
            pass
 
243
        else:
 
244
            buf = ctypes.create_unicode_buffer(UNLEN + 1)
 
245
            n = ctypes.c_int(UNLEN + 1)
 
246
            if GetUserName(buf, ctypes.byref(n)):
 
247
                return buf.value
 
248
    # otherwise try env variables
 
249
    return get_environ_unicode('USERNAME')
 
250
 
 
251
 
 
252
# 1 == ComputerNameDnsHostname, which returns "The DNS host name of the local
 
253
# computer or the cluster associated with the local computer."
 
254
_WIN32_ComputerNameDnsHostname = 1
 
255
 
 
256
 
 
257
def get_host_name():
 
258
    """Return host machine name.
 
259
    If name cannot be obtained return None.
 
260
 
 
261
    :return: A unicode string representing the host name.
 
262
    """
 
263
    if has_ctypes_win32:
 
264
        try:
 
265
            kernel32 = ctypes.windll.kernel32
 
266
        except AttributeError:
 
267
            pass  # Missing the module we need
 
268
        else:
 
269
            buf = ctypes.create_unicode_buffer(MAX_COMPUTERNAME_LENGTH + 1)
 
270
            n = ctypes.c_int(MAX_COMPUTERNAME_LENGTH + 1)
 
271
 
 
272
            # Try GetComputerNameEx which gives a proper Unicode hostname
 
273
            GetComputerNameEx = getattr(kernel32, 'GetComputerNameExW', None)
 
274
            if (GetComputerNameEx is not None
 
275
                and GetComputerNameEx(_WIN32_ComputerNameDnsHostname,
 
276
                                      buf, ctypes.byref(n))):
 
277
                return buf.value
 
278
    return get_environ_unicode('COMPUTERNAME')
 
279
 
 
280
 
 
281
def _ensure_with_dir(path):
 
282
    if (not os.path.split(path)[0] or path.startswith(u'*')
 
283
            or path.startswith(u'?')):
 
284
        return u'./' + path, True
 
285
    else:
 
286
        return path, False
 
287
 
 
288
 
 
289
def _undo_ensure_with_dir(path, corrected):
 
290
    if corrected:
 
291
        return path[2:]
 
292
    else:
 
293
        return path
 
294
 
 
295
 
 
296
def glob_one(possible_glob):
 
297
    """Same as glob.glob().
 
298
 
 
299
    work around bugs in glob.glob()
 
300
    - Python bug #1001604 ("glob doesn't return unicode with ...")
 
301
    - failing expansion for */* with non-iso-8859-* chars
 
302
    """
 
303
    corrected_glob, corrected = _ensure_with_dir(possible_glob)
 
304
    glob_files = glob.glob(corrected_glob)
 
305
 
 
306
    if not glob_files:
 
307
        # special case to let the normal code path handle
 
308
        # files that do not exist, etc.
 
309
        glob_files = [possible_glob]
 
310
    elif corrected:
 
311
        glob_files = [_undo_ensure_with_dir(elem, corrected)
 
312
                      for elem in glob_files]
 
313
    return [elem.replace(u'\\', u'/') for elem in glob_files]
 
314
 
 
315
 
 
316
def glob_expand(file_list):
 
317
    """Replacement for glob expansion by the shell.
 
318
 
 
319
    Win32's cmd.exe does not do glob expansion (eg ``*.py``), so we do our own
 
320
    here.
 
321
 
 
322
    :param file_list: A list of filenames which may include shell globs.
 
323
    :return: An expanded list of filenames.
 
324
 
 
325
    Introduced in breezy 0.18.
 
326
    """
 
327
    if not file_list:
 
328
        return []
 
329
    expanded_file_list = []
 
330
    for possible_glob in file_list:
 
331
        expanded_file_list.extend(glob_one(possible_glob))
 
332
    return expanded_file_list
 
333
 
 
334
 
 
335
def get_app_path(appname):
 
336
    r"""Look up in Windows registry for full path to application executable.
 
337
    Typically, applications create subkey with their basename
 
338
    in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\
 
339
 
 
340
    :param  appname:    name of application (if no filename extension
 
341
                        is specified, .exe used)
 
342
    :return:    full path to aplication executable from registry,
 
343
                or appname itself if nothing found.
 
344
    """
 
345
    import _winreg
 
346
 
 
347
    basename = appname
 
348
    if not os.path.splitext(basename)[1]:
 
349
        basename = appname + '.exe'
 
350
 
 
351
    try:
 
352
        hkey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,
 
353
                               'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\' +
 
354
                               basename)
 
355
    except EnvironmentError:
 
356
        return appname
 
357
 
 
358
    try:
 
359
        try:
 
360
            path, type_id = _winreg.QueryValueEx(hkey, '')
 
361
        except WindowsError:
 
362
            return appname
 
363
    finally:
 
364
        _winreg.CloseKey(hkey)
 
365
 
 
366
    if type_id == REG_SZ:
 
367
        return path
 
368
    if type_id == REG_EXPAND_SZ and has_win32api:
 
369
        fullpath = win32api.ExpandEnvironmentStrings(path)
 
370
        if len(fullpath) > 1 and fullpath[0] == '"' and fullpath[-1] == '"':
 
371
            fullpath = fullpath[1:-1]   # remove quotes around value
 
372
        return fullpath
 
373
    return appname
 
374
 
 
375
 
 
376
def set_file_attr_hidden(path):
 
377
    """Set file attributes to hidden if possible"""
 
378
    if not has_ctypes_win32:
 
379
        return
 
380
    from ctypes.wintypes import BOOL, DWORD, LPCWSTR
 
381
    _kernel32 = ctypes.windll.kernel32
 
382
    # <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw>
 
383
    _SetFileAttributesW = ctypes.WINFUNCTYPE(BOOL, LPCWSTR, DWORD)(
 
384
        ("SetFileAttributesW", _kernel32))
 
385
    FILE_ATTRIBUTE_HIDDEN = 2
 
386
    if not SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN):
 
387
        e = ctypes.WinError()
 
388
        from . import trace
 
389
        trace.mutter('Unable to set hidden attribute on %r: %s', path, e)
 
390
 
 
391
 
 
392
def _command_line_to_argv(command_line, argv, single_quotes_allowed=False):
 
393
    """Convert a Unicode command line into a list of argv arguments.
 
394
 
 
395
    It performs wildcard expansion to make wildcards act closer to how they
 
396
    work in posix shells, versus how they work by default on Windows. Quoted
 
397
    arguments are left untouched.
 
398
 
 
399
    :param command_line: The unicode string to split into an arg list.
 
400
    :param single_quotes_allowed: Whether single quotes are accepted as quoting
 
401
                                  characters like double quotes. False by
 
402
                                  default.
 
403
    :return: A list of unicode strings.
 
404
    """
 
405
    # First, split the command line
 
406
    s = cmdline.Splitter(
 
407
        command_line, single_quotes_allowed=single_quotes_allowed)
 
408
 
 
409
    # Bug #587868 Now make sure that the length of s agrees with sys.argv
 
410
    # we do this by simply counting the number of arguments in each. The counts should
 
411
    # agree no matter what encoding sys.argv is in (AFAIK)
 
412
    # len(arguments) < len(sys.argv) should be an impossibility since python gets
 
413
    # args from the very same PEB as does GetCommandLineW
 
414
    arguments = list(s)
 
415
 
 
416
    # Now shorten the command line we get from GetCommandLineW to match sys.argv
 
417
    if len(arguments) < len(argv):
 
418
        raise AssertionError("Split command line can't be shorter than argv")
 
419
    arguments = arguments[len(arguments) - len(argv):]
 
420
 
 
421
    # Carry on to process globs (metachars) in the command line
 
422
    # expand globs if necessary
 
423
    # TODO: Use 'globbing' instead of 'glob.glob', this gives us stuff like
 
424
    #       '**/' style globs
 
425
    args = []
 
426
    for is_quoted, arg in arguments:
 
427
        if is_quoted or not glob.has_magic(arg):
 
428
            args.append(arg)
 
429
        else:
 
430
            args.extend(glob_one(arg))
 
431
    return args
 
432
 
 
433
 
 
434
if has_ctypes_win32:
 
435
    def get_unicode_argv():
 
436
        prototype = ctypes.WINFUNCTYPE(ctypes.c_wchar_p)
 
437
        GetCommandLineW = prototype(("GetCommandLineW",
 
438
                                     ctypes.windll.kernel32))
 
439
        command_line = GetCommandLineW()
 
440
        if command_line is None:
 
441
            raise ctypes.WinError()
 
442
        # Skip the first argument, since we only care about parameters
 
443
        argv = _command_line_to_argv(command_line, sys.argv)[1:]
 
444
        return argv
 
445
 
 
446
    def get_environ_unicode(key, default=None):
 
447
        """Get `key` from environment as unicode or `default` if unset
 
448
 
 
449
        The environment is natively unicode on modern windows versions but
 
450
        Python 2 only accesses it through the legacy bytestring api.
 
451
 
 
452
        Environmental variable names are case insenstive on Windows.
 
453
 
 
454
        A large enough buffer will be allocated to retrieve the value, though
 
455
        it may take two calls to the underlying library function.
 
456
        """
 
457
        cfunc = getattr(get_environ_unicode, "_c_function", None)
 
458
        if cfunc is None:
 
459
            from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR
 
460
            cfunc = ctypes.WINFUNCTYPE(DWORD, LPCWSTR, LPWSTR, DWORD)(
 
461
                ("GetEnvironmentVariableW", ctypes.windll.kernel32))
 
462
            get_environ_unicode._c_function = cfunc
 
463
        buffer_size = 256  # heuristic, 256 characters often enough
 
464
        while True:
 
465
            buf = ctypes.create_unicode_buffer(buffer_size)
 
466
            length = cfunc(key, buf, buffer_size)
 
467
            if not length:
 
468
                code = ctypes.GetLastError()
 
469
                if code == 203:  # ERROR_ENVVAR_NOT_FOUND
 
470
                    return default
 
471
                raise ctypes.WinError(code)
 
472
            if buffer_size > length:
 
473
                return buf[:length]
 
474
            buffer_size = length
 
475
 
 
476
 
 
477
if has_ctypes_win32:
 
478
    from ctypes.wintypes import BOOL, DWORD, HANDLE
 
479
    _kernel32 = ctypes.windll.kernel32
 
480
    _CloseHandle = ctypes.WINFUNCTYPE(BOOL, HANDLE)(
 
481
        ("CloseHandle", _kernel32))
 
482
    _OpenProcess = ctypes.WINFUNCTYPE(HANDLE, DWORD, BOOL, DWORD)(
 
483
        ("OpenProcess", _kernel32))
 
484
 
 
485
    def _ctypes_is_local_pid_dead(pid):
 
486
        """True if pid doesn't correspond to live process on this machine"""
 
487
        handle = _OpenProcess(1, False, pid)  # PROCESS_TERMINATE
 
488
        if not handle:
 
489
            errorcode = ctypes.GetLastError()
 
490
            if errorcode == 5:  # ERROR_ACCESS_DENIED
 
491
                # Probably something alive we're not allowed to kill
 
492
                return False
 
493
            elif errorcode == 87:  # ERROR_INVALID_PARAMETER
 
494
                return True
 
495
            raise ctypes.WinError(errorcode)
 
496
        _CloseHandle(handle)
 
497
        return False
 
498
 
 
499
    is_local_pid_dead = _ctypes_is_local_pid_dead