Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mackup/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Support platforms
PLATFORM_DARWIN = 'Darwin'
PLATFORM_LINUX = 'Linux'
PLATFORM_WINDOWS = 'Windows'

# Directory containing the application configs
APPS_DIR = 'applications'
Expand Down
83 changes: 79 additions & 4 deletions mackup/mackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
import os.path
import shutil
import tempfile
import sys
import platform

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):
Expand All @@ -26,12 +33,80 @@ 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):
Expand Down
26 changes: 22 additions & 4 deletions mackup/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,13 @@ 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. 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):
Expand Down Expand Up @@ -200,7 +206,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()
Expand Down Expand Up @@ -377,6 +388,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):
Expand All @@ -385,7 +403,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:
Expand All @@ -407,7 +425,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

Expand Down