From ef9b5cc7f3548b50dcb8b4c87dcafeef2a3b6d6c Mon Sep 17 00:00:00 2001 From: imdano <> Date: Mon, 24 Mar 2008 00:03:35 +0000 Subject: [PATCH] Added distro-specific init scripts based on those used by NM (these are very experimental and likely broken in many cases). Updated setup.py to pick which initscript to install based on the distro detected. Updated MANIFEST.in to make sure launchdaemon.sh is included in the sdist build. Fixed a bunch of crash bugs in tool detection system when tools are detected. Made tool detection work correctly when "which" returns output if no match is found (as opposed to no output). Eventually we might want to hardcode possible paths instead of using which at all... Fixed some message formatting in the daemon. Added some docstrings. Added a pidfile system for increased initscript compatibility (sort of, it's somewhat incomplete). --- MANIFEST.in | 1 + daemon.py | 47 ++++++--- misc.py | 64 +----------- monitor.py | 22 ++-- networking.py | 15 ++- other/50-wicd-suspend.sh | 3 + other/80-wicd-connect.sh | 4 + other/hammer-00186ddbac.desktop | 12 +++ other/initscripts/arch/wicd | 63 ++++++++++++ other/initscripts/debian/wicd | 68 +++++++++++++ other/initscripts/gentoo/wicd | 44 ++++++++ other/initscripts/redhat/wicd | 75 ++++++++++++++ other/initscripts/slackware/wicd | 88 ++++++++++++++++ other/initscripts/suse/wicd | 49 +++++++++ other/wicd.conf | 17 ++++ other/wicd.png | Bin 0 -> 4000 bytes setup.py | 166 ++++++++++++++----------------- wnettools.py | 17 +++- 18 files changed, 567 insertions(+), 188 deletions(-) create mode 100755 other/50-wicd-suspend.sh create mode 100755 other/80-wicd-connect.sh create mode 100755 other/hammer-00186ddbac.desktop create mode 100644 other/initscripts/arch/wicd create mode 100755 other/initscripts/debian/wicd create mode 100644 other/initscripts/gentoo/wicd create mode 100644 other/initscripts/redhat/wicd create mode 100644 other/initscripts/slackware/wicd create mode 100644 other/initscripts/suse/wicd create mode 100755 other/wicd.conf create mode 100755 other/wicd.png diff --git a/MANIFEST.in b/MANIFEST.in index 77fabca..6d6ab9e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,4 @@ recursive-include other * recursive-include encryption * recursive-include images * recursive-include translations * +include launchdaemon.sh diff --git a/daemon.py b/daemon.py index a39d8f5..287a1fc 100644 --- a/daemon.py +++ b/daemon.py @@ -172,6 +172,7 @@ class ConnectionWizard(dbus.service.Object): print "autoconnecting...", str(self.GetWirelessInterface()) self.AutoConnect(True) else: + self.Scan() print "--no-scan detected, not autoconnecting..." ########## DAEMON FUNCTIONS @@ -440,8 +441,8 @@ class ConnectionWizard(dbus.service.Object): def GetAutoReconnect(self): """ Returns the value of self.auto_reconnect. See SetAutoReconnect. """ do = bool(self.auto_reconnect) - return self.__printReturn('returning automatically reconnect when\ - connection drops', do) + return self.__printReturn('returning automatically reconnect when ' \ + + 'connection drops', do) @dbus.service.method('org.wicd.daemon') def SetAutoReconnect(self, value): @@ -567,7 +568,7 @@ class ConnectionWizard(dbus.service.Object): state - info contents: NOT_CONNECTED - info[0] = "" CONNECTING - info[0] = "wired" or "wireless" - info[1] = None for wired, essid for wireless + info[1] = None if wired, an essid if wireless WIRED - info[0] = IP Adresss WIRELESS - info[0] = IP Address info[1] = essid @@ -719,8 +720,8 @@ class ConnectionWizard(dbus.service.Object): """ Sets property to value in network specified. """ # We don't write script settings here. if (property.strip()).endswith("script"): - print "Setting script properties through the daemon is not \ - permitted." + print "Setting script properties through the daemon is not" \ + + " permitted." return False self.LastScan[networkid][property] = misc.Noneify(value) #end function SetProperty @@ -729,7 +730,10 @@ class ConnectionWizard(dbus.service.Object): def DetectWirelessInterface(self): """ Returns an automatically detected wireless interface. """ iface = self.wifi.DetectWirelessInterface() - print 'automatically detected wireless interface ' + iface + if iface: + print 'automatically detected wireless interface ' + iface + else: + print "Couldn't detect a wireless interface." return str(iface) @dbus.service.method('org.wicd.daemon.wireless') @@ -877,7 +881,7 @@ class ConnectionWizard(dbus.service.Object): @dbus.service.method('org.wicd.daemon.wired') def CheckWiredConnectingMessage(self): - """ Returns the wired interface\'s status message. """ + """ Returns the wired interface's status message. """ if self.wired.connecting_thread: return self.wired.connecting_thread.GetStatus() else: @@ -887,8 +891,8 @@ class ConnectionWizard(dbus.service.Object): def SetWiredProperty(self, property, value): if self.WiredNetwork: if (property.strip()).endswith("script"): - print "Setting script properties through the daemon \ - is not permitted." + print "Setting script properties through the daemon" \ + + " is not permitted." return False self.WiredNetwork[property] = misc.Noneify(value) return True @@ -1404,10 +1408,11 @@ Arguments: \t-f\t--no-daemon\tDon't daemonize (run in foreground). \t-e\t--no-stderr\tDon't redirect stderr. \t-o\t--no-stdout\tDon't redirect stdout. +\t-P\t--pidfile path\tCreate a pidfile at the specified path. \t-h\t--help\t\tPrint this help. """ -def daemonize(): +def daemonize(write_pid, pidfile): """ Disconnect from the controlling terminal. Fork twice, once to disconnect ourselves from the parent terminal and a @@ -1436,7 +1441,10 @@ def daemonize(): try: pid = os.fork() if pid > 0: - print "wicd daemon: pid " + str(pid) + if not write_pid: + print "wicd daemon: pid " + str(pid) + else: + print >> open(pidfile,'wt'), str(pid) sys.exit(0) except OSError, e: print >> sys.stderr, "Fork #2 failed: %d (%s)" % (e.errno, e.strerror) @@ -1456,13 +1464,16 @@ def main(argv): auto_scan = True try: - opts, args = getopt.getopt(sys.argv[1:], 'feos', - ['help', 'no-daemon', 'no-stderr', 'no-stdout', 'no-scan']) + opts, args = getopt.getopt(sys.argv[1:], 'feosP:', + ['help', 'no-daemon', 'no-stderr', 'no-stdout', 'no-scan', + 'pidfile:']) except getopt.GetoptError: # Print help information and exit usage() sys.exit(2) - + + write_pid = False + pid_file = None for o, a in opts: if o in ('-h', '--help'): usage() @@ -1475,12 +1486,16 @@ def main(argv): do_daemonize = False if o in ('-s', '--no-scan'): auto_scan = False + if o in ('-P', '--pidfile'): + write_pid = True + pid_file = a - if do_daemonize: daemonize() - + if do_daemonize: daemonize(write_pid, pid_file) + if redirect_stderr or redirect_stdout: output = LogWriter() if redirect_stdout: sys.stdout = output if redirect_stderr: sys.stderr = output + time.sleep(1) print '---------------------------' print 'wicd initializing...' diff --git a/misc.py b/misc.py index 91d842f..3a5f073 100644 --- a/misc.py +++ b/misc.py @@ -253,7 +253,8 @@ def get_gettext(): if (osLanguage): langs += osLanguage.split(":") langs += ["en_US"] - lang = gettext.translation('wicd', local_path, languages=langs, fallback=True) + lang = gettext.translation('wicd', local_path, languages=langs, + fallback=True) _ = lang.gettext return _ @@ -276,63 +277,4 @@ def error(parent, message): gtk.BUTTONS_OK) dialog.set_markup(message) dialog.run() - dialog.destroy() - - -class LogWriter: - """ A class to provide timestamped logging. """ - def __init__(self): - self.file = open(wpath.log + 'wicd.log','a') - self.eol = True - self.logging_enabled = True - - def __del__(self): - self.file.close() - - - def write(self, data): - """ Writes the data to the log with a timestamp. - - This function handles writing of data to a log file. In order to - handle output redirection, we need to be careful with how we - handle the addition of timestamps. In any set of data that is - written, we replace the newlines with a timestamp + new line, - except for newlines that are the final character in data. - - When a newline is the last character in data, we set a flag to - indicate that the next write should have a timestamp prepended - as well, which ensures that the timestamps match the time at - which the data is written, rather than the previous write. - - Keyword arguments: - data -- The string to write to the log. - - """ - #global logging_enabled - data = data.encode('utf-8') - if len(data) <= 0: return - if self.logging_enabled: - if self.eol: - self.file.write(self.get_time() + ' :: ') - self.eol = False - - if data[-1] == '\n': - self.eol = True - data = data[:-1] - - self.file.write( - data.replace('\n', '\n' + self.get_time() + ' :: ')) - if self.eol: self.file.write('\n') - self.file.close() - - def get_time(self): - """ Return a string with the current time nicely formatted. - - The format of the returned string is yyyy/mm/dd HH:MM:SS - - """ - x = time.localtime() - return ''.join([ - str(x[0]).rjust(4,'0'), '/', str(x[1]).rjust(2,'0'), '/', - str(x[2]).rjust(2,'0'), ' ', str(x[3]).rjust(2,'0'), ':', - str(x[4]).rjust(2,'0'), ':', str(x[5]).rjust(2,'0')]) + dialog.destroy() \ No newline at end of file diff --git a/monitor.py b/monitor.py index 5bf27d5..f899686 100755 --- a/monitor.py +++ b/monitor.py @@ -1,5 +1,13 @@ #!/usr/bin/env python +""" monitor -- connection monitoring process + +This process is spawned as a child of the daemon, and is responsible +for monitoring connection status and initiating autoreconnection +when appropriate. + + +""" # # Copyright (C) 2007 Adam Blackburn # Copyright (C) 2007 Dan O'Reilly @@ -19,7 +27,6 @@ import dbus import gobject -import os import sys import time from dbus.mainloop.glib import DBusGMainLoop @@ -34,8 +41,8 @@ if sys.platform == 'linux2': libc = dl.open('/lib/libc.so.6') libc.call('prctl', 15, 'wicd-monitor\0', 0, 0, 0) # 15 is PR_SET_NAME except: - pass - + print 'Failed to set the process name' + if __name__ == '__main__': wpath.chdir(__file__) @@ -244,13 +251,16 @@ class ConnectionStatus(): self.reconnecting = False def reply_handle(): - pass + """ Just a dummy function needed for asynchronous dbus calls. """ + pass -def err_handle(e): - pass +def err_handle(error): + """ Just a dummy function needed for asynchronous dbus calls. """ + pass def main(): + """ Start the connection monitor and set the updater to run every 2 sec. """ monitor = ConnectionStatus() gobject.timeout_add(3000, monitor.update_connection_status) diff --git a/networking.py b/networking.py index 810c67c..bae3385 100644 --- a/networking.py +++ b/networking.py @@ -56,14 +56,6 @@ if __name__ == '__main__': class Controller(object): """ Parent class for the different interface types. """ - connecting_thread = None - before_script = None - after_script = None - disconnect_script = None - driver = None - wiface = None - liface = None - def __init__(self): """ Initialise the class. """ self.global_dns_1 = None @@ -74,6 +66,13 @@ class Controller(object): self._dhcp_client = None self._flush_tool = None self._debug = None + self.connecting_thread = None + self.before_script = None + self.after_script = None + self.disconnect_script = None + self.driver = None + self.wiface = None + self.liface = None def set_wireless_iface(self, value): self._wireless_interface = value diff --git a/other/50-wicd-suspend.sh b/other/50-wicd-suspend.sh new file mode 100755 index 0000000..2e3a6ab --- /dev/null +++ b/other/50-wicd-suspend.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# Bring wifi network interface back up. +/opt/wicd/suspend.py diff --git a/other/80-wicd-connect.sh b/other/80-wicd-connect.sh new file mode 100755 index 0000000..6d56084 --- /dev/null +++ b/other/80-wicd-connect.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Bring wifi network interface back up. + +/opt/wicd/autoconnect.py diff --git a/other/hammer-00186ddbac.desktop b/other/hammer-00186ddbac.desktop new file mode 100755 index 0000000..491f275 --- /dev/null +++ b/other/hammer-00186ddbac.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Application;Network; +Encoding=UTF-8 +Exec=/opt/wicd/gui.py +GenericName=Network Manager +Icon=/opt/wicd/images/wicd.png +Icon[en_US]=/opt/wicd/images/wicd.png +Name=Wicd +Name[en_US]=Wicd +Terminal=false +Type=Application +Version=1.0 diff --git a/other/initscripts/arch/wicd b/other/initscripts/arch/wicd new file mode 100644 index 0000000..b440ddf --- /dev/null +++ b/other/initscripts/arch/wicd @@ -0,0 +1,63 @@ +#!/bin/bash + +# +# Copyright (C) 2007 Dan O'Reilly +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License Version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +WICD_BIN=$/opt/wicd/daemon.py +CMD=$0 +ARG=$1 + +# general config +. /etc/rc.conf +. /etc/rc.d/functions +# Sanity checks. +[ -x $WICD_BIN ] || exit 0 +set -- $(ps ax | grep 'python.*$WICD_BIN') +PID=$1 +case "$ARG" in + start) + stat_busy "Starting wicd" + if [ -z "$PID" ]; then + $WICD_BIN + fi + if [ ! -z "$PID" -o $? -gt 0 ]; then + stat_fail + else + add_daemon wicd + stat_done + fi + ;; + stop) + stat_busy "Stopping wicd" + [ ! -z "$PID" ] && kill $PID &> /dev/null + if [ $? -gt 0 ]; then + stat_fail + else + rm_daemon wicd + stat_done + fi + ;; + restart) + $CMD stop + sleep 1 + $CMD start + ;; + *) + echo "usage: $CMD {start|stop|restart}" + ;; +esac +exit 0 + diff --git a/other/initscripts/debian/wicd b/other/initscripts/debian/wicd new file mode 100755 index 0000000..11d3900 --- /dev/null +++ b/other/initscripts/debian/wicd @@ -0,0 +1,68 @@ +#! /bin/sh +# +# wicd wicd daemon +# Daemon for managing network connections. +# This file should be placed in /etc/init.d. +# +# Authors: Dan O'Reilly +# +# Version: @(#)skeleton 2.85-23 28-Jul-2004 miquels@cistron.nl +# + +set -e + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DESC="Network connection manager daemon" +NAME="wicd" +PROCNAME=wicd-daemon +DAEMON=/opt/wicd/daemon.py +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +USER=root + +# Gracefully exit if the package has been removed. +test -x $DAEMON || exit 0 + +# +# Function that starts the daemon/service. +# +d_start() { + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --user $USER --exec $DAEMON -- $DAEMON_OPTS +} + +# +# Function that stops the daemon/service. +# +d_stop() { + start-stop-daemon --stop --quiet --pidfile $PIDFILE \ + --oknodo --user $USER --name $PROCNAME +} + + +case "$1" in + start) + echo -n "Starting $NAME:" + d_start + echo " Done." + ;; + stop) + echo -n "Stopping $NAME:" + d_stop + echo " Done." + ;; + restart|force-reload) + echo -n "Restarting $NAME:" + d_stop + sleep 1 + d_start + echo " Done." + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 + diff --git a/other/initscripts/gentoo/wicd b/other/initscripts/gentoo/wicd new file mode 100644 index 0000000..609ab54 --- /dev/null +++ b/other/initscripts/gentoo/wicd @@ -0,0 +1,44 @@ +#!/sbin/runscript +# +# wicd: wicd daemon +# +# chkconfig: 345 98 02 +# description: This is a daemon for managing network connections. +# +# processname: wicd +# pidfile: /var/run/wicd.pid +# +### BEGIN INIT INFO +# Provides: $network +### END INIT INFO +WICD_BIN=/opt/wicd/daemon.py +NAME=wicd-daemon + +# Sanity checks. +[ -x $WICD_BIN ] || exit 0 + +# so we can rearrange this easily +processname=$WICD_BIN +pidfile=/var/run/wicd.pid +processargs="-P ${pidfile}" + +start() +{ + if [ -e ${pidfile} ]; then + rm -f ${pidfile} + fi + ebegin "Starting wicd" + start-stop-daemon --start --quiet --exec ${processname} -- ${processargs} + eend $? +} + +stop() +{ + ebegin "Stopping wicd" + start-stop-daemon --stop --quiet --name ${NAME} --pidfile ${pidfile} + eend $? + if [ -e ${pidfile} ]; then + rm -f $pidfile + fi +} + diff --git a/other/initscripts/redhat/wicd b/other/initscripts/redhat/wicd new file mode 100644 index 0000000..35d1e00 --- /dev/null +++ b/other/initscripts/redhat/wicd @@ -0,0 +1,75 @@ +#!/bin/sh +# +# wicd: wicd daemon +# +# chkconfig: 345 20 89 +# description: This is a daemon for managing network connections. +# +# processname: wicd +# pidfile: /var/run/wicd/wicd.pid +# + +WICD_BIN=/opt/wicd/daemon.py + +# Sanity checks. +[ -x $WICD_BIN ] || exit 11 + +# Source function library. +. /etc/rc.d/init.d/functions + +# so we can rearrange this easily +processname=$WICD_BIN +servicename=wicd +pidfile=/var/run/wicd/wicd.pid + +RETVAL=0 + +start() +{ + echo -n $"Starting wicd daemon: " + daemon --check $servicename $processname --pid-file=$pidfile + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename +} + +stop() +{ + echo -n $"Stopping wicd daemon: " + killproc -p $pidfile $servicename + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/$servicename + rm -f $pidfile + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status -p $pidfile $processname + RETVAL=$? + ;; + restart) + stop + start + ;; + condrestart) + if [ -f /var/lock/subsys/$servicename ]; then + stop + start + fi + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart}" + ;; +esac +exit $RETVAL + diff --git a/other/initscripts/slackware/wicd b/other/initscripts/slackware/wicd new file mode 100644 index 0000000..cd7e2a0 --- /dev/null +++ b/other/initscripts/slackware/wicd @@ -0,0 +1,88 @@ +#!/bin/sh +# +# wicd: wicd daemon +# +# description: This is a daemon managing network connections. +# +# processname: wicd-daemon +# pidfile: /var/run/wicd.pid +# +WICD_BIN=/opt/wicd/daemon.py + +# Sanity checks. +[ -x $WICD_BIN ] || exit 0 + +PIDFILE=/var/run/wicd.pid +$WICD_EXEC="$WICD_BIN -P $PIDFILE" + + +wicd_start() +{ + if [ "`pgrep dbus-daemon`" = "" ]; then + echo "D-BUS must be running to start wicd" + return + fi + + # Just in case the pidfile is still there, we may need to nuke it. + if [ -e "$PIDFILE" ]; then + rm -f $PIDFILE + fi + + echo "Starting wicd daemon: $WICD_BIN" + $WICD_EXEC +} + +wicd_status() +{ + local pidlist=`cat $PIDFILE 2>/dev/null` + if [ -z "$pidlist" ]; then + return 1 + fi + local command=`ps -p $pidlist -o comm=` + if [ "$command" != 'wicd-daemon' ]; then + return 1 + fi +} + +wicd_stop() +{ + echo -en "Stopping wicd: " + local pidlist=`cat $PIDFILE 2>/dev/null` + if [ ! -z "$pidlist" ]; then + kill $pidlist &>/dev/null + rm -f $PIDFILE &>/dev/null + fi + echo "stopped"; +} + +wicd_restart() +{ + wicd_stop + wicd_start +} + +case "$1" in +'start') + if ( ! wicd_status ); then + wicd_start + else + echo "wicd is already running (will not start it twice)." + fi + ;; +'stop') + wicd_stop + ;; +'restart') + wicd_restart + ;; +'status') + if ( wicd_status ); then + echo "wicd is currently running" + else + echo "wicd is not running." + fi + ;; +*) + echo "usage $0 start|stop|status|restart" +esac + diff --git a/other/initscripts/suse/wicd b/other/initscripts/suse/wicd new file mode 100644 index 0000000..a1b7dc0 --- /dev/null +++ b/other/initscripts/suse/wicd @@ -0,0 +1,49 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: wicd-daemon +# Required-Start: dbus +# Default-Start: 3 4 5 +# Default-Stop: +# Description: wicd, a wired and wireless connection manager. +### END INIT INFO + +WICD_BIN=/opt/wicd/daemon.py +test -x $WICD_BIN || exit 5 + +. /etc/rc.status +rc_reset + +case "$1" in + start) + checkproc $WICD_BIN + if [ $? = 0 ]; then + echo -n "wicd already running" + rc_status -v + rc_exit + fi + echo -n "Starting wicd" + startproc $WICD_BIN + rc_status -v + ;; + stop) + echo -n "Shutting down wicd" + killproc -TERM $WICD_BIN + rc_status -v + ;; + restart) + $0 stop + $0 start + rc_status + ;; + status) + echo -n "Checking for wicd: " + checkproc $WICD_BIN + rc_status -v + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 + ;; +esac + diff --git a/other/wicd.conf b/other/wicd.conf new file mode 100755 index 0000000..cc905e3 --- /dev/null +++ b/other/wicd.conf @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/other/wicd.png b/other/wicd.png new file mode 100755 index 0000000000000000000000000000000000000000..460b80f01f463a2ec6f222bc3896b47289867a26 GIT binary patch literal 4000 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