From f3f476bced5aab20d8283fd71d9c53b944d8dcdf Mon Sep 17 00:00:00 2001 From: neutralevil Date: Sat, 31 Oct 2015 20:59:11 +0800 Subject: [PATCH 1/3] Add primary support for Windows --- mackup/constants.py | 1 + mackup/mackup.py | 81 ++++++++++++++++++++++++++++++++++++++++++--- mackup/utils.py | 22 +++++++++--- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/mackup/constants.py b/mackup/constants.py index 68cf73c25..def0aeeba 100644 --- a/mackup/constants.py +++ b/mackup/constants.py @@ -5,6 +5,7 @@ # Support platforms PLATFORM_DARWIN = 'Darwin' PLATFORM_LINUX = 'Linux' +PLATFORM_WINDOWS = 'Windows' # Directory containing the application configs APPS_DIR = 'applications' diff --git a/mackup/mackup.py b/mackup/mackup.py index a91a1e135..bb06853cd 100644 --- a/mackup/mackup.py +++ b/mackup/mackup.py @@ -9,10 +9,14 @@ import os.path import shutil import tempfile +import sys +import platform +from ctypes import Structure, windll, c_ulong, c_ulonglong, c_void_p, byref from . import utils from . import config from . import appsdb +from . import constants class Mackup(object): @@ -26,12 +30,81 @@ def __init__(self): self.mackup_folder = self._config.fullpath self.temp_folder = tempfile.mkdtemp(prefix="mackup_tmp_") + + def is_link_privilege_enabled(self): + """Check if symbolic link creation privilege is enabled.""" + TOKEN_ALL_ACCESS = c_ulong(0x000f01ff) + SE_CREATE_SYMBOLIC_LINK_NAME = 'SeCreateSymbolicLinkPrivilege' + class LUID_AND_ATTRIBUTES(Structure): + _fields_ = [ + ("Luid", c_ulonglong), + ("Attributes", c_ulong)] + class PRIVILEGE_SET(Structure): + _fields_ = [ + ("PrivilegeCount", c_ulong), + ("Control", c_ulong), + ("Privilege", LUID_AND_ATTRIBUTES)] + + try: + token = c_void_p(None) + ret = windll.advapi32.OpenProcessToken( + windll.kernel32.GetCurrentProcess(), + TOKEN_ALL_ACCESS, + byref(token)) + if ret == 0: + return False + + luid = c_ulonglong(0) + ret = windll.advapi32.LookupPrivilegeValueW( + None, SE_CREATE_SYMBOLIC_LINK_NAME, byref(luid)) + if ret == 0: + return False + + enabled = c_ulong(0) + priv_set = PRIVILEGE_SET(1, 1, LUID_AND_ATTRIBUTES(luid, 2)) + ret = windll.advapi32.PrivilegeCheck( + token, byref(priv_set), byref(enabled)) + return ret != 0 and enabled.value > 0 + + except OSError: + return false + + + def check_link_support_on_windows(self): + """Check if symbolic links can be created on Windows.""" + # Symbolic link support was introduced in Windows 6.0 + if sys.getwindowsversion()[0] < 6: + utils.error("Mackup can only run on Windows 6.0 (Vista)" + " or higher.") + + # Added support for Windows symbolic links in version 3.2 + if sys.version_info[0] < 3 or sys.version_info[1] < 2: + utils.error("Mackup need Python version 3.2 or highter.") + + if not self.is_link_privilege_enabled(): + utils.error("Mackup can not work without the" + " SeCreateSymbolicLinkPrivilege.\n" + "To grant this privilege, you have two choices:\n" + " 1. Log in with an administrator account and" + " launch mackup within a command\n" + " prompt running as administrator.\n" + " 2. Use a standard account, and assign the" + " SeCreateSymbolicLinkPrivilege to\n" + " your user or group via the local group policy" + " editor.") + + def check_for_usable_environment(self): """Check if the current env is usable and has everything's required.""" - # Do not let the user run Mackup as root - if os.geteuid() == 0: - utils.error("Running Mackup as a superuser is useless and" - " dangerous. Don't do it!") + + if platform.system() == constants.PLATFORM_WINDOWS: + # Symbolic link support is silly on Windows, so check it first. + self.check_link_support_on_windows() + else: + # Do not let the user run Mackup as root + if os.geteuid() == 0: + utils.error("Running Mackup as a superuser is useless and" + " dangerous. Don't do it!") # Do we have a folder to put the Mackup folder ? if not os.path.isdir(self._config.path): diff --git a/mackup/utils.py b/mackup/utils.py index e20d33a72..9ae261482 100644 --- a/mackup/utils.py +++ b/mackup/utils.py @@ -142,7 +142,9 @@ def link(target, link_to): chmod(target) # Create the link to target - os.symlink(target, link_to) + # target_is_directory should be specified on Windows, + # and is ignored on non-Windows platform + os.symlink(target, link_to, target_is_directory=os.path.isdir(target)) def chmod(target): @@ -200,7 +202,12 @@ def get_dropbox_folder_location(): Returns: (str) Full path to the current Dropbox folder """ - host_db_path = os.path.join(os.environ['HOME'], '.dropbox/host.db') + host_db_path = os.path.expanduser('~/.dropbox/host.db') + if (not os.path.isfile(host_db_path) + and platform.system() == constants.PLATFORM_WINDOWS): + roaming = os.path.expanduser('~/AppData/Roaming/Dropbox/host.db') + local = os.path.expanduser('~/AppData/Local/Dropbox/host.db') + host_db_path = local if os.path.isfile(local) else roaming try: with open(host_db_path, 'r') as f_hostdb: data = f_hostdb.read().split() @@ -377,6 +384,13 @@ def remove_immutable_attribute(path): elif (platform.system() == constants.PLATFORM_LINUX and os.path.isfile('/usr/bin/chattr')): subprocess.call(['/usr/bin/chattr', '-R', '-i', path]) + elif platform.system() == constants.PLATFORM_WINDOWS: + # It's impossible to process a nonempty folder + # and its children all at once + subprocess.call(['attrib', '-H', '-R', path]) + if os.path.isdir(path) and os.listdir(path): + subprocess.call([ + 'attrib', '-H', '-R', os.path.join(path, '*'), '/S', '/D']) def can_file_be_synced_on_current_platform(path): @@ -385,7 +399,7 @@ def can_file_be_synced_on_current_platform(path): Check if it makes sense to sync the file at the given path on the current platform. - For now we don't sync any file in the ~/Library folder on GNU/Linux. + For now we only sync files in the ~/Library folder on OS X. There might be other exceptions in the future. Args: @@ -407,7 +421,7 @@ def can_file_be_synced_on_current_platform(path): # not any file/folder named LibrarySomething library_path = os.path.join(os.environ['HOME'], 'Library/') - if platform.system() == constants.PLATFORM_LINUX: + if platform.system() != constants.PLATFORM_DARWIN: if fullpath.startswith(library_path): can_be_synced = False From 05cf62512d749c7c1e3707f6e41acda630fbcc0a Mon Sep 17 00:00:00 2001 From: neutralevil Date: Sat, 31 Oct 2015 21:40:52 +0800 Subject: [PATCH 2/3] PEP8 compliance --- mackup/mackup.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/mackup/mackup.py b/mackup/mackup.py index bb06853cd..d9b894bca 100644 --- a/mackup/mackup.py +++ b/mackup/mackup.py @@ -30,45 +30,45 @@ def __init__(self): self.mackup_folder = self._config.fullpath self.temp_folder = tempfile.mkdtemp(prefix="mackup_tmp_") - def is_link_privilege_enabled(self): """Check if symbolic link creation privilege is enabled.""" TOKEN_ALL_ACCESS = c_ulong(0x000f01ff) SE_CREATE_SYMBOLIC_LINK_NAME = 'SeCreateSymbolicLinkPrivilege' + class LUID_AND_ATTRIBUTES(Structure): - _fields_ = [ - ("Luid", c_ulonglong), - ("Attributes", c_ulong)] + _fields_ = [ + ("Luid", c_ulonglong), + ("Attributes", c_ulong)] + class PRIVILEGE_SET(Structure): _fields_ = [ - ("PrivilegeCount", c_ulong), - ("Control", c_ulong), - ("Privilege", LUID_AND_ATTRIBUTES)] + ("PrivilegeCount", c_ulong), + ("Control", c_ulong), + ("Privilege", LUID_AND_ATTRIBUTES)] try: token = c_void_p(None) ret = windll.advapi32.OpenProcessToken( - windll.kernel32.GetCurrentProcess(), - TOKEN_ALL_ACCESS, - byref(token)) + windll.kernel32.GetCurrentProcess(), + TOKEN_ALL_ACCESS, + byref(token)) if ret == 0: return False luid = c_ulonglong(0) ret = windll.advapi32.LookupPrivilegeValueW( - None, SE_CREATE_SYMBOLIC_LINK_NAME, byref(luid)) + None, SE_CREATE_SYMBOLIC_LINK_NAME, byref(luid)) if ret == 0: return False enabled = c_ulong(0) priv_set = PRIVILEGE_SET(1, 1, LUID_AND_ATTRIBUTES(luid, 2)) ret = windll.advapi32.PrivilegeCheck( - token, byref(priv_set), byref(enabled)) + token, byref(priv_set), byref(enabled)) return ret != 0 and enabled.value > 0 except OSError: - return false - + return False def check_link_support_on_windows(self): """Check if symbolic links can be created on Windows.""" @@ -93,7 +93,6 @@ def check_link_support_on_windows(self): " your user or group via the local group policy" " editor.") - def check_for_usable_environment(self): """Check if the current env is usable and has everything's required.""" From d724febbc5bf3d5419df0b3ebee128462bd132c7 Mon Sep 17 00:00:00 2001 From: Liu Bin Date: Sun, 1 Nov 2015 10:54:43 +0800 Subject: [PATCH 3/3] Fixed test failure --- mackup/mackup.py | 5 ++++- mackup/utils.py | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mackup/mackup.py b/mackup/mackup.py index d9b894bca..d1d9807ed 100644 --- a/mackup/mackup.py +++ b/mackup/mackup.py @@ -12,12 +12,15 @@ import sys import platform -from ctypes import Structure, windll, c_ulong, c_ulonglong, c_void_p, byref from . import utils from . import config from . import appsdb from . import constants +if platform.system() == constants.PLATFORM_WINDOWS: + from ctypes import Structure, c_ulong, c_ulonglong, c_void_p, byref + from ctypes import windll + class Mackup(object): diff --git a/mackup/utils.py b/mackup/utils.py index 9ae261482..a6ee57ed8 100644 --- a/mackup/utils.py +++ b/mackup/utils.py @@ -142,9 +142,13 @@ def link(target, link_to): chmod(target) # Create the link to target - # target_is_directory should be specified on Windows, - # and is ignored on non-Windows platform - os.symlink(target, link_to, target_is_directory=os.path.isdir(target)) + # target_is_directory should be specified on Windows, and is ignored on + # non-Windows platform. This feature is added in Python 3.2 + if sys.version_info[0] > 3 and sys.version_info[1] > 2: + isdir = os.path.isdir(target) + os.symlink(target, link_to, target_is_directory=isdir) + else: + os.symlink(target, link_to) def chmod(target):