В статье описан способ автоматического отключения/включения InternetSharing, который препятствует переходу Macbook в спящий режим или вновь пробуждает ноутбук, если вручную всё же его усыпить.
Предистория
Работаю на Macbook, в помещении 3G отсутствует. При подключении ноутбука к сети через RJ-45, удобно использовать InternetSharing (далее IS), так как некоторая работа связанна с мобильными устройствами. Однажды настроив IS забыл о проблеме с интернетом на телефоне, однако стал замечать, когда прихожу на работу, ноутбук включен. Первый раз не придал значения. На второй раз решил посмотреть в логах, когда он же он включается. Оказалось, что включается он спустя пару минут, как я его перевожу в спящий режим.
Как оказалось службой IS блокирует сон в то время как он активен.
Команда
pmset -g assertions
укажет, что виновен именно IS:
pid 70000: [0x000…000] PreventSystemSleep named: «com.apple.InternetSharing».
Я стал искать как люди решают подобную проблему, но натыкался только на «отключите службу».
Пришлось решать проблему самому.
Что же делать?
Как оказалось – настроенная служба IS запускается и останавливается, используя файл:
/System/Library/LaunchDaemons/com.apple.InternetSharing.plist
Решение оказалось таким: включать службу при пробуждении и выключать при переходе в режим сна, то есть использовать такие команды:
- для включения
sudo launchctl load -F /System/Library/LaunchDaemons/com.apple.InternetSharing.plist
Флаг -F обозначает форсированный старт, так как служба имеет флаг
<key>Disabled</key> <true/>
; - для выключения
sudo launchctl unload /System/Library/LaunchDaemons/com.apple.InternetSharing.plist
.
Но для этих операций требуется ввод пароля пользователя с правами администратора.
Осталось узнать «Как запускать программы при пробуждении и при переходе в сон» и при этом не требовали бы ввод пароля.
Решение
Первое – сделаем свою службу, которая будет запускаться с правами администратора – то есть не требовать ввод пароля для launchctl.
Второе – будет отправлять нашей службе команды при пробуждении и переходе в сон, соответственно start и stop.
Итак, служба будет прослушивать порт, например, 10001 и ждать команды start и stop.
Напишем службу на PHP (полный код ниже), соответственно для запуска IS и его остановки будут производиться следующие действия:
для запуска:
shell_exec('launchctl load -F /System/Library/LaunchDaemons/com.apple.InternetSharing.plist');
для остановки:
shell_exec('launchctl unload /System/Library/LaunchDaemons/com.apple.InternetSharing.plist');
Для запуска нашей службы надо поместить файл com.username.InternetSharing.plist
в папку /Library/LaunchDaemons/
.
Запустить командой:
sudo launchctl load -F /Library/LaunchDaemons/com.username.InternetSharing.plist
Для того чтобы отловить события пробуждения и переход в сон воспользуемся программой Scenario
. Напишем для неё на AppleScripts два скрипа, которые будут вызывать клиентскую программу(полный код ниже) с параметром start или stop.
Скрипт запуска
do shell script "/usr/bin/php /Users/username/CheckMac/client.php start"
Скрипт остановки
do shell script "/usr/bin/php /Users/username/CheckMac/client.php stop"
Поместим их в специальные директории программы Scenario:
"~/Library/Scenario/Wake Scripts"
и "~/Library/Scenario/Sleep Scripts"
.
Результат
При включении программа Scenario запускает скрипт из папки "Wake Scripts
", который запускает Клиентскую программу с параметром start. Клиентская программа связывается с нашей службой, которая может запустить службу IS, так как обладает достаточными правами.
При переходе в режим сна выполняется аналогичная операция, только с папкой "Sleep Scripts
" и с параметром stop.
Код
Служба
<?php // PHP 5.3.10
// Исходные настройки
error_reporting(E_ALL ^ E_WARNING);
set_time_limit(0);
ob_implicit_flush();
$port = 10001;
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($sock, "127.0.0.1", $port) or die('Port listened'."n");
socket_set_option($sock, SOL_SOCKET, SO_KEEPALIVE, 1);
socket_set_nonblock($sock);
socket_listen($sock, 1000);
$clients = array($sock);
$tarr = array();
$iparr = array();
$current = 0;
while (true) {
$read = $clients;
if (count($tarr) > 0) foreach ($tarr as $ind => $tim) {
// Если подключение не проявляет активность 10 секунд - отключать
if ((time() - $tim) > 10) {
socket_close($read[$ind + 1]);
unset($clients[$ind+1]);
unset($iparr[$ind]);
unset($tarr[$ind]);
echo "Disconnect client.n";
continue;
}
}
// Если нет клиентов - ждать
if (socket_select($read, $write = NULL, $except = NULL, 1) < 1)
continue;
// Новое подключение
if (in_array($sock, $read)) {
$current ++;
$clients[$current] = $newsock = socket_accept($sock);
socket_write($newsock, "<OK>n");
socket_getpeername($newsock, $ip);
echo "New connection from ip: {$ip}n";
$key = array_search($sock, $read);
$iparr[$current-1] = $ip;
$tarr[$current-1] = time();
unset($read[$key]);
$read[$current] = $newsock;
continue;
}
// Проверить подключения
foreach ($read as $index => $read_sock) {
$data = socket_read($read_sock, 1024);
if ($data === false) {
$key = array_search($read_sock, $clients);
unset($clients[$key]);
unset($iparr[$key - 1]);
echo "Disconnect client.n";
unset($tarr[$key - 1]);
continue;
}
$data = trim($data);
// Команды для обработки
if (!empty($data)) {
echo $iparr[$index - 1] . "[$index] - $datan";
$tarr[$index - 1] = time();
switch ($data) {
case "quit":
socket_close($read_sock);
$key = array_search($read_sock, $clients);
unset($clients[$key]);
unset($iparr[$key - 1]);
echo "Disconnect client.n";
unset($tarr[$key - 1]);
break;
case "ping":
socket_write($read_sock, "<PONG> " . time() . "n");
break;
// Получили команду на запуск IS
case 'start':
$s = shell_exec('launchctl list');
if(strpos($s, 'com.apple.InternetSharing') === false){
shell_exec('launchctl load -F /System/Library/LaunchDaemons/com.apple.InternetSharing.plist');
}
break;
// Получили команду на останов IS
case 'stop':
$s = shell_exec('launchctl list');
if(strpos($s, 'com.apple.InternetSharing') !== false){
shell_exec('launchctl unload /System/Library/LaunchDaemons/com.apple.InternetSharing.plist');
}
break;
}
if ($data === "close") {
socket_close($sock);
break(2);
}
}
}
}
socket_close($sock);
com.username.InternetSharing.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.username.InternetSharing</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/php</string>
<string>/Users/username/CheckMac/listen.php</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Клиентская программа
<?php
$fp = fsockopen("127.0.0.1", 10001, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />n";
} else {
$out = @$argv[1]."rn";
fwrite($fp, $out);
fgets($fp, 128);
sleep(1); // Иначе команды воспринимаются как единый текст
$out = "quitrn";
fwrite($fp, $out);
fgets($fp, 128);
fclose($fp);
}
Скрипт запуска IS (Не текстовой файл – создаётся программой AppleScripts)
do shell script "/usr/bin/php /Users/username/CheckMac/client.php start"
Скрипт оастнова IS
do shell script "/usr/bin/php /Users/username/CheckMac/client.php stop"
Автор: truekenny