From e9bdb65ade01294aa702ebbf40a3e050bd0a244b Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Fri, 21 Oct 2016 15:38:58 -0700 Subject: [PATCH] Add new Sierra-compatible fileshare mounting code to manifestutil. --- code/client/manifestutil | 141 ++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 15 deletions(-) diff --git a/code/client/manifestutil b/code/client/manifestutil index de5484a5..64a46d9a 100755 --- a/code/client/manifestutil +++ b/code/client/manifestutil @@ -32,6 +32,8 @@ import sys import thread import time +import objc + from ctypes.util import find_library from xml.parsers.expat import ExpatError @@ -43,6 +45,105 @@ except ImportError: '''Placeholder if munkilib is not available''' return 'UNKNOWN' +# NetFS share mounting code borrowed and liberally adapted from Michael Lynn's +# work here: https://gist.github.com/pudquick/1362a8908be01e23041d +try: + import errno + import getpass + import objc + from CoreFoundation import CFURLCreateWithString + + class Attrdict(dict): + '''Custom dict class''' + __getattr__ = dict.__getitem__ + __setattr__ = dict.__setitem__ + + NetFS = Attrdict() + # Can cheat and provide 'None' for the identifier, it'll just use + # frameworkPath instead + # scan_classes=False means only add the contents of this Framework + NetFS_bundle = objc.initFrameworkWrapper( + 'NetFS', frameworkIdentifier=None, + frameworkPath=objc.pathForFramework('NetFS.framework'), + globals=NetFS, scan_classes=False) + + # https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ + # ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html + # Fix NetFSMountURLSync signature + del NetFS['NetFSMountURLSync'] + objc.loadBundleFunctions( + NetFS_bundle, NetFS, [('NetFSMountURLSync', 'i@@@@@@o^@')]) + NETFSMOUNTURLSYNC_AVAILABLE = True +except (ImportError, KeyError): + NETFSMOUNTURLSYNC_AVAILABLE = False + +if NETFSMOUNTURLSYNC_AVAILABLE: + class ShareMountException(Exception): + '''An exception raised if share mounting failed''' + pass + + + class ShareAuthenticationNeededException(ShareMountException): + '''An exception raised if authentication is needed''' + pass + + + def mount_share(share_url): + '''Mounts a share at /Volumes, returns the mount point or raises an + error''' + sh_url = CFURLCreateWithString(None, share_url, None) + # Set UI to reduced interaction + open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI} + # Allow mounting sub-directories of root shares + mount_options = {NetFS.kNetFSAllowSubMountsKey: True} + # Mount! + result, output = NetFS.NetFSMountURLSync( + sh_url, None, None, None, open_options, mount_options, None) + # Check if it worked + if result != 0: + if result in (errno.ENOTSUP, errno.EAUTH): + # errno.ENOTSUP is returned if an afp share needs a login + # errno.EAUTH is returned if authentication fails (SMB for sure) + raise ShareAuthenticationNeededException() + raise ShareMountException( + 'Error mounting url "%s": %s, error %s' + % (share_url, os.strerror(result), result)) + # Return the mountpath + return str(output[0]) + + + def mount_share_with_credentials(share_url, username, password): + '''Mounts a share at /Volumes, returns the mount point or raises an + error. Include username and password as parameters, not in the + share_path URL''' + sh_url = CFURLCreateWithString(None, share_url, None) + # Set UI to reduced interaction + open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI} + # Allow mounting sub-directories of root shares + mount_options = {NetFS.kNetFSAllowSubMountsKey: True} + # Mount! + result, output = NetFS.NetFSMountURLSync( + sh_url, None, username, password, open_options, mount_options, None) + # Check if it worked + if result != 0: + raise ShareMountException( + 'Error mounting url "%s": %s, error %s' + % (share_url, os.strerror(result), result)) + # Return the mountpath + return str(output[0]) + + + def mount_share_url(share_url): + '''Mount a share url under /Volumes, prompting for password if needed + Raises ShareMountException if there's an error''' + try: + mount_share(share_url) + except ShareAuthenticationNeededException: + username = raw_input('Username: ') + password = getpass.getpass() + mount_share_with_credentials(share_url, username, password) + + try: # PyLint cannot properly find names inside Cocoa libraries, so issues bogus # No name 'Foo' in module 'Bar' warnings. Disable them. @@ -337,22 +438,32 @@ def mount_repo_cli(): repo_url = pref('repo_url') if os.path.exists(repo_path): return - os.mkdir(repo_path) - print 'Attempting to mount fileshare %s:' % repo_url - if repo_url.startswith('afp:'): - cmd = ['/sbin/mount_afp', '-i', repo_url, repo_path] - elif repo_url.startswith('smb:'): - cmd = ['/sbin/mount_smbfs', repo_url[4:], repo_path] - elif repo_url.startswith('nfs://'): - cmd = ['/sbin/mount_nfs', repo_url[6:], repo_path] + print 'Attempting to mount fileshare %s:' % repo_path + if NETFSMOUNTURLSYNC_AVAILABLE: + # mount the share using the NetFS API + try: + mount_share_url(repo_url) + except ShareMountException, err: + print >> sys.stderr, err + else: + WE_MOUNTED_THE_REPO = True else: - print >> sys.stderr, 'Unsupported filesystem URL!' - return - retcode = subprocess.call(cmd) - if retcode: - os.rmdir(repo_path) - else: - WE_MOUNTED_THE_REPO = True + # do it the old way + os.mkdir(REPO_PATH) + if REPO_URL.startswith('afp:'): + cmd = ['/sbin/mount_afp', '-i', repo_url, repo_path] + elif REPO_URL.startswith('smb:'): + cmd = ['/sbin/mount_smbfs', repo_url[4:], repo_path] + elif REPO_URL.startswith('nfs://'): + cmd = ['/sbin/mount_nfs', repo_url[6:], repo_path] + else: + print >> sys.stderr, 'Unsupported filesystem URL!' + return + retcode = subprocess.call(cmd) + if retcode: + os.rmdir(repo_path) + else: + WE_MOUNTED_THE_REPO = True def unmount_repo_cli():