Pull to refresh
0
Content AI
Решения для интеллектуальной обработки информации

Как подружить Mac OS X с Microsoft DFS

Reading time 17 min
Views 10K
Хотя это корпоративный блог ABBYY, в этой заметке я не коснусь наших продуктов и нашей компании, а расскажу про решение одного практического вопроса: доступ к сетевым ресурсам из Mac OS X в ситуации, когда в компании применяется Microsoft DFS (Distributed File System). В крупных компаниях эта технология используется на каждом шагу, и ABBYY не исключение. Действительно, вместо разрозненной системы серверов с разбросанными по ним ресурсами, пользователь видит логически выстроенное дерево сетевых ресурсов.

Когда лет пять тому назад я решился поменять свой рабочий ноутбук на MacBook Pro, в коллективе это было встречено неоднозначно. Особенно скептически на это смотрели в команде системных администраторов. Хотя я и руководил на протяжении последних пяти лет этой командой, мне полагались совсем незначительные поблажки, а использование Mac OS, с точки зрения наших админов, выходило за рамки просто поблажек. Но я сказал, что все проблемы я возьму на себя и почти сдержал свое слово, хотя, признаюсь, иногда все же приходилось досаждать админов своими маковскими проблемами. Но проблему с DFS мне удалось решить самому, потому что уж очень она меня достала.



Проблема доступа к DFS обсуждается в форумах давно, все заинтересованные лица уже несколько лет ждут нормальной поддержки этой технологии от Apple. Но, к сожалению, воз и ныне там (в Купертино, по всей видимости).

Обрисую вкратце проблему: мне регулярно приходят письма с ссылками в формате UNC на файлы, примерно такие:

\\dfs\Common\Media\Pictures\New Year Party\Aram on his head.jpg

Последние версии Outlook начали при этом автоматом вставлять в письма URL-ссылку, которая выглядит примерно так:

file:////dfs/Common/Media/Pictures/New%20Year%20Party/…

Для меня это медвежья услуга, так как Mac OS не знает, по какому протоколу обращаться по этой ссылке. Если вдруг повезло, Outlook ссылку сам не поставил, то Mail.app в Snow Leopard умеет автоматом конвертировать UNC-пути в представление smb://server/share/path. Хорошая функция, но чтобы она помогла, надо чтобы очень повезло: исходный путь не должен быть к DFS-ресурсу и в HTML-тексте не должна быть подложена ссылка. К тому же, энтузиазм Mail.app обрывается на первом встреченном пробеле, поэтому, очень часто такие автоматические ссылки оказываются еще и битыми. Но даже открыть в Finder окно «Подключение к серверу», ручками скопировать исходный текст, поправить все обратные слеши на прямые, то все получится только если исходный путь — это реальный путь к реальному компьютеру, а не DFS-путь.

Надо признать, что глобальное решение этой проблемы есть, называется ADmitMac но оно стоит немалых денег и делает много чего другого, что скорее вредно, чем полезно, потому что эта программа слишком глубоко лезет в систему.

Второе, более простое решение, называется Diffiss, похоже на экспериментальную работу начинающего программиста. Продукт позволяет перемещаться по дереву DFS, правда, до момента закрытия главного окна приложения, которое после этого открыть невозможно никак иначе, кроме как перезапуском приложения. Эта программа позволяет не держать в голове соответствие узлов DFS реальным серверам, и ходить по дереву DFS, но никак не помогает открывать ссылки из писем. Приходится ручками ходить по дереву, сначала внутри Diffiss, затем внутри Finder-а. Но на какое-то время даже это было облегчением.

Недавно я почти случайно случайно натолкнулся на вот эту статью, которая заставила меня взяться за основательное решение проблемы, то есть сделать основательные костыли, для того, чтобы доковылять до времени, когда, наконец, Apple поддержит DFS в самой системе на уровне Finder-а. Кстати, в Samba 3.0.28, поставляемой с OS X Snow Leopard, работа с DFS поддержана (чем я, собственно, и воспользовался).

Костыли состоят из двух деталей. Одна — bash-скрипт, который получает на вход имя корневого сервера DFS и исходный путь, а на выходе выдает UNC-путь к каталогу на реальном сервере. Вторая деталь — небольшой скрипт на AppleScript, который получает у пользователя исходный путь, запускает bash-скрипт и открывает нужный каталог в Finder.

Рассмотрим каждую из деталей отдельно.

#!/bin/bash
 
# Этот скрипт предназначен для преобразования DFS-пути к ресурсу 
# в путь к реальному разделенному ресурсу в сети Windows
 
# Скрипт ожидает на входе :
# arg1: имя корневого сервера DFS 
# arg2: путь UNC, который надо привести к реальному пути
# Скрипт предполагает, что аутентификация пользователей производится через Kerberos. 
 
if [ -z "$1" ] || [ -z "$2" ]then
    echo -e "Ошибка: скрипт предполагает два аргумента."
    echo 
    exit 100
fi
 
DFS_SERVER=$1
 
# Конвертируем обратные слеши в прямые, удаляем возможные пробелы в начале 
# строки и переводим все буквы в строчные
DFS_PATH=$(echo -n $2 | sed 's/\\/\//g' | sed 's/^ *//g' | tr "[:upper:]" "[:lower:]")
real_share=""
exit_code="0"
 
# Получаем список DFS узлов c помощью rpcclient (см. выражение в конце цикла, 
# сразу после 'done'). Такой способ вызова команды сделан для того, чтобы избежать 
# использования каналов, так как для реализации каналов цикл выполняется в подоболочке, 
# которая маскирует переменные, которые мы хотим модицифировать в цикле.
 
while read smb_path; do
    read comment
    read state
    read num_stores
    # Отделяем поля от меток, удаляем пробелы в начале, и переводим буквы в строчные 
    smb_path=$(echo $smb_path | awk -F: '{ gsub(/^[ \t]+/, "", $2); print $2 }' | \
        tr "[:upper:]" "[:lower:]")
    num_stores=$(echo $num_stores | awk -F: '{ gsub(/^[ \t]+/, "", $2); print $2 }')
    state=$(echo $state | awk -F: '{ gsub(/^[ \t]+/, "", $2); print $2 }')
 
    # Проверка того, что в переменные num_stores и state занесены цифры и 
    # что они не пустые. Если нет, то, по всей видимости, произошел сбой
    expr "$num_stores + $state" > /dev/null 2> /dev/null
 
    if [ $? !0 ] || [ -z "$num_stores" ] || [ -z "$state" ]then
        echo -e "Ошибка при попытке получить список узлов DFS."
        exit_code="200"
        break
    fi
 
    # Количество реальных ресурсов, привязанных к узлу, может быть больше одного.
    # В таких случаях мы будем ипользовать вызов smbclient для выбора ресурса.
    for ((store = 0; store < $num_stores; store++))do
        read server
        read share
    done
 
    # Проверяем, совпадает ли начало DFS-пути с каким-либо из ресурсов 
    # из полученного списка
    if [ "$state" -eq "1" ] && [[ "$DFS_PATH" == "$smb_path"* ]]then
        if [ "$num_stores" -gt "1" ]then
            # Если количество ресурсов на узел больше одного, вызываем smbclient. 
            # Выдача состоит из двух строк, нас интересует вторая
            while read buff; do
                if [ ! -z "$buff" ] && [[ "$buff" == "//"* ]]then 
                    real_share="$buff"
                else 
                    # Если вторая строка не выглядит 
                    # как UNC-путь к ресурсу, значит, произошла ошибка
                    echo -e "Ошибка при вызове smbclient: $buff"
                    exit_code="300"
                fi
            done < <(smbclient -k -c showconnect $smb_path 2> /dev/null)
        else
            server=$(echo $server | awk -F: '{ gsub(/^[ \t]+/, "", $2); print $2 }' )
            share=$(echo $share | awk -F: '{ gsub(/^[ \t]+/, "", $2); print $2 }')
            real_share="//$server/$share"
        fi
        # Добавляем к реальному серверу и ресурсу остаток UNC-пути
        if [ ! -z "$real_share" ]then
            smb_path_len="${#smb_path}"
            rest_of_path=$( awk -v len="$smb_path_len" -v awk_string="$DFS_PATH" \
                'BEGIN {print substr(awk_string, len + 1) }' )
            real_share="$real_share$rest_of_path"
        fi
        # Мы ищем до первого совпадения, поэтому покидаем цикл
        break
    fi
done < <(rpcclient -k --command="dfsenum 3" $DFS_SERVER | sed 's/\\/\//g')
 
# Если переменной real_share не было присвоено никакое значение (скорее всего, потому, 
# что исходный путь не был найден в списке, или потому что возникла ошибка, то 
# возвращаем исходный путь
if [ -z "$real_share" ]then
    real_share=$DFS_PATH
fi
 
# Для возврата результата в AppleScript нам нужно вывести его 
# в стандартный выходной поток
echo $real_share
 
exit $exit_code
 


Скрипт, конечно, не идеален, все наверняка можно сделать лучше. Я вообще редко пишу скрипты, пользователю Mac OS они обычно не нужны, поэтому руку пока не набил. Технические детали понятны из комментариев, а по сути делает он следующее:
  • используя программу rpcclient, входящую в состав пакета Samba, мы узнаем у корневого сервера DFS таблице узлов, с привязкой к реальным серверам. Здесь надо заметить, что в скрипте предполагается аутентификация через Kerberos, что, скорее всего, означает, что ваш компьютер занесен в домен и вы уже вошли в систему с доменным именем и паролем. Без этого жить в среде Windows-серверов совсем туго.
  • дальше мы разбираем полученную таблицу (в приведенной выше статье описана структура выдачи rpcclient, поэтому я не буду ее здесь повторять). По мере разбора узлов ищем тот, который совпадает с началом нашего исходного пути. Как только находим, дальнейшие поиски останавливаем, составляем из частей реальный путь и выводим его командой echo. Это стандартный способ передачи результатов работы из оболочки в AppleScript.


Во всей этой истории есть одна особенность. Дело в том, что DFS содержит встроенные механизмы дублирования, позволяющие хранить зеркальные копии данных на разных серверах. Поэтому одному узлу могут соответствовать несколько реальных ресурсов на различных серверах. Можно было бы не мудрить и просто взять один из них (если ничего не менять в коде, то возьмется последний), но я решил воспользоваться тем выбором, который сделала бы Samba, для чего в таких случаях запускается smbclient c единственной командой, которая возвращает путь к ресурсу на сервере, к которому надо подключаться. Кстати, я подозреваю, что Samba выбирает узлы случайным образом (код не смотрел), ну да ладно.

Теперь, когда мы разобрались с первым скриптом, перейдем ко второму. Он написан так, чтобы его можно было бы запускать как с командной строки (с помощью osascript), так и интерактивно. На самом деле есть еще третий способ запуска, о нем чуть ниже.

-- Этот скрипт подключает в Finder сетевой ресурс Windows по DFS-пути к ресурсу. 
-- Скрипт вызывает внешний bash-скрипт для преобразования DFS-пути в путь 
-- к реальному разделенному ресурсу
 
property DFSRootServer : "dfs.mycompany" -- сюда вставляем имя корневого сервера DFS
property BashScriptPath : "/Users/Shared/Scripts/dfs_to_share" -- путь к нашему bash-скрипту
property isRunFromCommandLine : true
 
-- Этот обработчик используется программой ThisService для организации 
-- вызова скрипта из меню "Службы". 
-- Также мы его вызываем из метода "run". В качестве аргумента передается исходный UNC-путь.
on process(input)
    try
        set realPath to do shell script "/bin/bash " & BashScriptPath & ¬
            " " & DFSRootServer & " '" & input & "'"
    on error errMessage number errNumber
        -- Наш скрипт возвращает разные коды ошибок в разных ситуациях, обрабатываем здесь
        if errNumber is 100 then
            set errMessage to "Внутренняя ошибка при выполнении скрипта."
        else if errNumber is 200 then
            set errMessage to "Невозможно получить таблицу DFS узлов от сервера. ¬
                Проверьте, есть ли у вас активный билет Kerberos."

        else if errNumber is 300 then
            set errMessage to "Ошибка использования smbclient для получения узла DFS."
        else
            set errMessage to "Неизвестная ошибка при выполнении скрипта оболочки: " & (errNumber as text)
        end if
        if isRunFromCommandLine then
            -- В случае, если приложение запущено из командной строки, 
            -- выводим сообщение в консоль
            log errMessage
        else
            display dialog errMessage buttons {"Close"}
        end if
        return
    end try
    try
        (* Самым удобным способом заставить Finder открыть  каталог по заданному пути является 
            использование метода "open location". Он автоматом монтирует устройство и 
            открывает в окне Finder нужный каталог. Но этот метод требует на вход 
            путь в URL-представлении, поэтому используем python для конвертации 
            UNC-пути в представление URL *)

        set urlOfPath to "smb:" & (do shell script ¬
            "python  -c 'import urllib, sys; print urllib.pathname2url(sys.argv[1])' " & ¬
            quoted form of realPath)
        tell application "Finder"
            open location urlOfPath
        end tell
    on error errMessage
        display dialog errMessage buttons {CloseButton}
    end try
end process
 
on run argv
    set ConnectButton to "Подключить"
    set CancelButton to "Отменить"
    -- Проверяем, не запускали ли нас с командной строки с передачей пути в качестве параметра
    if (count of argv) is equal to 0 then
        display dialog "Введите путь UNC:" default answer "" ¬
            buttons {ConnectButton, CancelButton} default button 1
        copy the result as list to {UNC_path, button_pressed}
 
        if button_pressed is ConnectButton and UNC_path is not "" then
            set isRunFromCommandLine to false
            process(UNC_path)
        end if
    else
        process(item 1 of argv)
    end if
end run
 


Метод run автоматически вызывается после запуска скрипта, в нем выдается единственное окно, в которое пользователю предлагают ввести исходный путь к нужному ресурсу. Основная функциональность скрипта заключена в методе «process». Он получает на вход реальный путь к серверу, запускает описанный выше bash-скрипт, интерпретирует результат и открывает полученный путь в программе Finder.

На этом, последнем, этапе возникает одна проблема. В Finder есть удобное диалоговое окно «Подключение к серверу» (Connect to Server). К сожалению, эта функциональность не доступна через AppleScript, поэтому, попробовав несколько различных вариантов, я остановился на методе «open location», который ожидает на входе URL. А для этого необходимо конвертировать возвращенную первым скриптом строку в URL-представление, заменив некоторые символы на их коды. К счастью, в комплект Mac OS X входит интерпретатор языка python с библиотеками, в которых есть удобный метод, который делает именно то, что нам нужно. Полученный URL мы скармливаем Finder-у и он волшебным образом открывает окошко с нужным нам каталогом.

Если вы попробуете работу этих скриптов, то заметите одну особенность: если подключаться к ресурсу через диалоговое окно Finder-а, то монтируется непосредственно разделенный ресурс на сервере, и в Finder-е можно ходить по всему дереву каталогов в этом ресурсе. А в результате работы описанного скрипта оказывается доступным только каталог, на который ссылается исходный путь, и его подкаталоги. Как кому, а мне это поведение нравится больше: ты получаешь то, что просишь, и ничего лишнего. Надо углубиться выше, можно взять часть пути и подключить только эту часть, либо выбрать подключенный сервер в боковой панели окна Finder, и от него уже идти вниз по иерархии.

Теперь про обещанный третий путь запуска скрипта. В программах на Cocoa доступно меню «Службы» (Services), и было бы очень удобно, если бы наш скрипт можно было вызвать из этого меню. Выделяете путь в тексте, выбираете пункт из этого меню (или нажимаете соответствующие этому пункту горящие клавиши) и открываете этот путь в Finder. Оказалось, что это сделать несложно, есть бесплатная программа (donationware), которая называется ThisService, которая позволяет подключить скрипт в меню «Службы». Именно для этого я поделил скрипт на два метода, так как для работы ThisService требуется, чтобы в скрипте был метод «process», принимающий на вход строку. После простой настройки все заработало.

Есть еще одна удобная программа и столь же бесплатная, называется Apptivate, которая позволяет назначить общесистемные горячие клавиши для запуска любых приложений, в том числе скриптов. Я привязал к комбинации клавиш запуск скрипта, и теперь могу в любой момент получить на экране свой вариант окна «Подключение к серверу».

Кстати, одно важное наблюдение, касающееся Snow Leopard. Если у вас уже есть действующий билет Kerberos, и вы отключаете все тома SMB, то билет у вас теряется. Это забавное поведение приводит к тому, что время от времени вы вдруг обнаруживаете, что не можете подключаться к объектам сети, хотя еще пять минут назад у вас все получалось. Это хорошо видно, если запустить программу «Просмотр билетов» (это можно сделать из «Связки ключей»), подключать и отключать тома, и смотреть, что происходит с вашим билетом. В результате, я решил всегда иметь ее под рукой, и завел на нее горячую клавишу в программе Apptivate.

В заключение я должен предупредить, что все описанное тестировалось только на моем компьютере (Mac OS X Snow Leopard, версия 10.6.4) и только в стенах компании ABBYY. Пишите в комментариях о любых неточностях, которые вы заметили, и предлагайте что можно улучшить. И я, и все, кому это интересно, будем только благодарны.
Tags:
Hubs:
+14
Comments 32
Comments Comments 32

Articles

Information

Website
www.contentai.ru
Registered
Founded
Employees
101–200 employees
Location
Россия