29 gennaio, 2007

Demoni in python

In questo post voglio proporvi un metodo più o meno standard per scrivere processi demoni in python. Un processo demone (per chi non lo sapesse), è un processo che di solito è in esecuzione nel sistema per fornire dei servizi su richiesta all'utente o ad altri processi. Il metodo standard per demonizzare un processo, consiste nell'usare due fork (si legge in giro che due fork garantiscono la correttezza del funzionamento su tutti i sistemi unix). La fork è una chiamata di sistema unix che crea in memoria una copia esatta del processo chiamante e restituisce il pid (process id) del nuovo processo al "genitore" e zero al figlio. Andiamo a vedere il codice:


import os
import sys

class Log:
def __init__(self, logfile):
self.logfile = logfile

def write(self, data):
self.logfile.write(data)
self.logfile.flush()

def daemonize(logfile):
"""
Questa funzione fara' diventare demone il processo che la richiamera'
e redirigera' il suo standard output e standard error sul file logfile
"""
# La maschera' con la quale verranno creati gli eventuali nuovi
# files
UMASK = 0

try:
pid = os.fork()
except OSError, e:
raise Exception, "la prima fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.setsid()
try:
pid = os.fork()
except OSError, e:
raise Exception, "la seconda fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.chdir('/')
os.umask(UMASK)
else:
os._exit(0)
else:
os._exit(0)

sys.stdin.close()
# Redirigo standard output ed error sul log file
try:
sys.stdout = sys.stderr = Log(open(logfile, 'a+'))
except IOError, details:
raise IOError, details

return 0

Un paio di punti da notare:
  • Il secondo figlio imposta come sua directory corrente la root ("/")
  • Lo stdin viene chiuso

Per quanto riguarda il primo punto, ciò viene fatto per prevenire situazioni anomale nelle quali un filesystem non può essere smontato in quanto c'è un processo che lo impedisce. Infatti avrete notato che se apriamo una shell e ci spostiamo con il comando cd in una directory sotto un filesystem montato quest'ultimo non potrà essere smontato finché la nostra shell non "smette" di impegnarlo. Il secondo punto è un po più ovvio: un demone non deve ricevere dati dallo standard input. Probabilmente se dovrà ricevere dati userà metodi differenti, come ad esempio i sockets.

Esempio d'uso


Possiamo salvare il codice appena visto in un modulo python che chiamiamo ad esempio daemon.py per poi richiamarlo in questo modo:

from daemon import daemonize

daemonize("/var/log/mylog.log")

import time
i = 0
while 1:
print i
time.sleep(1)
i = i + 1

Se salviamo questo semplice script in, ad esempio, test.py e lo eseguiamo (da root se vogliamo usare /var/log/mylog.log oppure possiamo cambiare file di log e usare un percorso scrivibile dall'utente non privilegiato, ad esempio la nostra home), quello che otterremo sarà un bel demone in esecuzione in background che ogni secondo andrà a scrivere sul nostro file di log il valore della variabile i. Il demone rimarrà in esecuzione fin quando non lo andremo a uccidere. Per farlo dobbiamo scoprire il suo pid con ps x (l'opzione x è necessaria per vedere i parametri passati ai processi), troviamo qualcosa di simile a 65100 python test.py e lo uccidiamo con kill 65100 (sostituire 65100 con il vostro pid.

26 gennaio, 2007

Controllo ortografico con aspell

Oggi ho scoperto una cosa che può risultare molto comoda. Siete anche voi amanti dei files di puro testo? Nutrite un'avversione intrinseca nei confronti ti tutti gli strumenti per l'ufficio? Ma sentite la mancanza di alcune funzioni come il controllo ortografico? Beh per quest'ultimo c'è rimedio. Infatti è possibile utilizzare il comando aspell direttamente sui files di puro testo.

Come funziona
Basta lanciare il comando:

aspell -c nomefile
L'opzione -c indica ad aspell di fare il check sul file. Si presenterà una schermata del tipo:


Come si può vedere l'interfaccia è molto semplice ed intuitiva (cliccate sull'immagine per vederla meglio). Ad esempio in questo caso basta premere il tasto 2 per correggere automaticamente l'errore. Le altre voci sono auto esplicative. Il tasto a in particolare aggiunge la parola nel proprio dizionario personale

24 gennaio, 2007

Thin Clients: Periferiche locali

Come ho già spiegato in posts precedenti qui e qui con ubuntu è molto semplice installare un terminal server. Utilizzando una installazione base però non è possibile utilizzare sui thin clients le periferiche locali. Ciò significa che ad esempio se inseriamo una pennina nella porta usb del thin client o un cd non possiamo utilizzarli. Per aggiungere questo supporto è necessario seguire queste istruzioni:

  • Installare sul terminal server il pacchetto ltspfs
  • caricare il modulo fuse con sudo modprobe fuse (aggiungerlo al file /etc/modules per far si che venga caricato ad ogni avvio del server)
  • Abilitare ltspfsd sui terminali aggiungendo LOCAL_STORAGE = Y al file di configurazione dei thin clients lts.conf

Se questo file non esiste in /opt/ltsp/i386/etc, ammesso che l'ambiente chroot dei thin clients si trovi in /opt/ltsp/i386, provvedere a crearlo manualmente con questo contenuto:

[default]
LOCALDEV=Y

Come funziona?

I thin clients a questo punto avranno in esecuzione il demone ltspfsd che si occuperà di esportare al server le periferiche che vengono inserite localmente. Non appena una periferica viene inserita, udev (sul thin clients) avvierà lo script /lib/udev/add_fstab_entry nell'ambiente chroot il quale richiama a sua volta /usr/sbin/ltspfsmounter sul server tramite ssh. Quest'ultimo si preoccuperà di montare il filesystem esportato da ltspfsd, nell'ambiente chroot in modo da renderlo disponibile ai thin clients.

21 gennaio, 2007

Unix sockets (AF_UNIX) o Internet sockets (AF_INET)?

Su un sistema unix, oltre ai socket internet esistono i socket unix. Ma quali sono le differenze tra queste due famiglie di sockets?

Internet Sockets
I socket internet servono per la comunicazione tra processi che si trovano sulla stessa macchina e/o, molto più spesso, su macchine differenti, magari anche distanti migliaia di Km. Tutti i servizi internet funzionano utilizzando questo tipo di sockets.

Unix Sockets
I socket della famiglia Unix (AF_UNIX), sono sockets simili agli internet socket. Essi usano un file speciale per realizzare la comunicazione tra processi. La differenza sostanziale è che non è possibile accedere a questo tipo di sockets da computer che non siano quello che ha creato il socket. In pratica quindi, questo genere di socket vengono utilizzati per realizzare l'IPC (inter-process communication) per processi che girano sulla stessa macchina.

Entrambe le famiglie poi supportano i socket stream (connessioni tcp) e i datagram sockets (connessioni udp)

20 gennaio, 2007

Esperimento: Una rete che si autoespande

In questo post voglio parlarvi di un esperimento che ho fatto in questi giorni con python. In pratica si tratta di una rete di nodi che si "auto trovano" nella rete mantenendo ognuno una lista con gli ip e i nomi degli altri nodi che viene aggiornata non appena un nodo viene a mancare. La cosa interessante è che non c'è un server centrale a gestire il tutto, ma ogni nodo è uguale all'altro ed ha la stessa importanza.

Come Funziona?
Ogni nodo (che in pratica è un programma python) è composto da 4 trhead ed un loop principale. Un thread (Broadcaster) si occupa di fare il broadcast sulla rete (N.B. quindi funziona solo sulla rete locale) della sua lista di contatti, che contiene l'ip e il nome degli altri nodi. Un secondo thread (Receiver) si occupa di ricevere il traffico broadcast inviato dagli altri nodi e di aggiornare la lista dei contatti. Un terzo thread (TcpListner) rimane in ascolto per connessioni tcp (questo serve agli altri nodi per capire se il nodo è ancora "vivo"). Infine un quarto thread (BlistCleaner) che si occupa di interrogare i nodi nella sua lista per scoprire se sono ancora attivi o meno e di aggiornare di conseguenza la sua lista di contatti.
La prima cosa che un nodo fa è quella di mandare in broadcast il suo ip e il suo nome (passati come parametri). Se ad esempio i nodi sono due, il secondo nodo cattura il broadcast del primo e lo inserisce nella sua lista di broadcast che contiene già se stesso e così via con gli altri nodi. Riporto di seguito il codice dei nodi:

PORT = 50000
TCPPORT = 80000
BCASTIP = '192.168.50.255'

import sys, time
import threading
import socket

class Broadcaster(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.data = data
self.exit = False

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', 0))
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

while 1:
if self.exit: break
data = ""
print "Broadcasting: ", self.data.broad
for item in self.data.broad:
name, ip = item
data += name + ":" + ip + ","
if data != "":
# tolgo l'ultima virgola
data = data[:len(data) - 1]
try:
s.sendto(data, (BCASTIP, PORT))
except socket.error, e:
break
print "Broadcaster error: ", e
time.sleep(2)
s.close()
print "Exit Broadcaster"

class Receiver(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.exit = False
self.data = data

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', PORT))
s.settimeout(0.1)
while 1:
if self.exit: break
try:
data, fromaddr = s.recvfrom(1024)
items = data.split(",")
self.data.rec = []
if len(items) > 0:
for item in items:
name, ip = item.split(":")
self.data.rec.append( (name, ip) )

print "Received: ", self.data.rec
except socket.timeout, e:
pass
except socket.error, e:
break
print "Receiver error: ", e


#print (data, fromaddr)
#print self.list

s.close()
print "Exit Receiver"

class TcpListner(threading.Thread):

def __init__(self, ip):
threading.Thread.__init__(self)
self.myip = ip
self.exit = False

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind( (self.myip, TCPPORT) )
s.listen(1)
s.settimeout(0.1)

while 1:
if self.exit: break
try:
conn, addr = s.accept()
except socket.timeout, e:
pass
except socket.error, e:
s.close()
break
print "TcpListner error: ", e

s.close()
print "Exit TcpListner"

class BlistCleaner(threading.Thread):

def __init__(self, data, myname):
threading.Thread.__init__(self)
self.data = data
self.exit = False
self.myname = myname

def run(self):

while 1:
if len(self.data.broad) == 0: continue
for item in self.data.broad:
name, ip = item
if name != self.myname:
#print "check name: ", name, "ip: ", ip
try:
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
s.connect((ip, TCPPORT))
s.close()
except socket.error, e:
s.close()
print "Elimino ",name
self.data.broad.remove( (name, ip) )
self.data.rec.remove( (name, ip) )
print "Lista: ", self.data.broad
time.sleep(1)
if self.exit: break

print "Exit BlistCleaner"

class Data:
pass

data = Data()
data.rec = []
data.broad = []


broad = Broadcaster(data)
broad.setName("Broadcaster")
rec = Receiver(data)
rec.setName("Receiver")
# aggiungo il mio nome
name = sys.argv[1]
myip = sys.argv[2]

data.broad.append( (name, myip) )

tcpl = TcpListner(myip)
tcpl.setName("TcpListner")
cleaner = BlistCleaner(data, name)
cleaner.setName("BlistCleaner")

tcpl.start()
cleaner.start()

broad.start()
rec.start()

t = time.time()
while 1:
try:
if len(data.rec) > 0:
for n in data.rec:
if n not in data.broad:
data.broad.append(n)
time.sleep(0.1)
now = time.time()
if now - t > 3:
#print "rlist: ", rec.list
#print "blist: ", broad.list
t = now
except KeyboardInterrupt:
broad.exit = True
rec.exit = True
tcpl.exit = True
cleaner.exit = True
sys.exit(0)

Naturalmente sono da impostare l'indirizzo di broadcast e se salviamo il file come node.py dobbiamo eseguirlo come:

python [nomenodo] [mio_ip]
Per ora è solo un esperimento ma magari in futuro potrebbe risultarmi utile per qualcosa o magari può servire a voi come spunto per una nuova idea!

18 gennaio, 2007

Creare un mirror di un sito con wget

Vi è mai capitato di trovare un manuale che vi interessa su internet ma non un tarball o un zip da scaricare per leggerlo in locale? Un metodo per scaricarlo ugualmente in modo semplice (senza dover scaricare una pagina per volta) è quello di utilizzare wget per fare il mirroring (in locale) della parte di sito che contiene il nostro manuale. Il comando da usare è il seguente:


wget -m -np http://www.example.com/book/

L'opzione -m serve ad indicare a wget che vogliamo fare un mirroring, mentre l'opzione -np (-no-parent) indica a wget di non "uscire" dalla directory specificata. L'opzione è molto utile nel caso che (come nell'esempio) si vuole scaricare solo una directory del sito. Se non utilizziamo questa opzione, se in una pagina della directory book ci fosse un link ad esempio alla directory example.com/test, wget seguirebbe il link e scaricherebbe anche quella.

15 gennaio, 2007

Ubuntu Feisty herd2

E' stata rilasciata la seconda versione di testing di Ubuntu Feisty. Cosa include? Oltre ai soliti aggiornamenti di gnome, gtk, wine e il resto aggiunge alcune novità importanti: la prima è l'adozione di telepathy (dal grande progetto freedesktop.org che apprezzo molto per il suo lavoro nel rendere GNU/linux e non solo un sistema operativo migliore), che è un framework per le comunicazioni in tempo reale, come instant messaging, chiamate vocali e videochiamate (sostituirà gaim un giorno? A me piace gaim solo che fa un po di tutto ma tutto in modo poco completo). Un'altra grande novità è l'inclusione della release candidate del nuovo kernel che include una caratteristica a mio avviso importante: kvm.
Kvm (Kernel-based Virtual Machine) promette la virtualizzazione in modo semplice tramite un modulo da caricare (kvm.ko) ed una applicazione in user space che è una versione modificata di qemu. A differenza di xen, permette di installare un sistema operativo senza apportargli alcuna modifica. Sarà possibile installare windows o un'altra distribuzione linux in una virtual machine dotata di rete. Sarà la volta buona che diciamo addio a Vmware? Non vedo l'ora di provarlo!

14 gennaio, 2007

Nuovo layout per il sito

Ero stanco di avere poco spazio per i miei post. Ogni volta dovevo stare attento a che il codice non sforasse e ho quindi deciso di cambiare completamente il layout del sito. Questo mi sembra molto semplice e pulito, mostra il necessario in modo più chiaro e leggibile. Spero che apprezzerete.

12 gennaio, 2007

Tip: Il mio ip da python

Come fare per ottenere l'indirizzo di una scheda di rete da un programma python? Una possibile risposta (funzionante solo su sistemi Unix purtroppo :( ) è la seguente:


import sys
import socket
import struct

# unix only
import fcntl

def getip(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])

print getip(sys.argv[1])

Questa funzione, dato il nome di una interfaccia (ed esempio eth0) , restituisce una stringa con l'ip assegnato a quella interfaccia.
Il problema è che il modulo fcntl che fornisce la funzione ioctl è disponibile solo su sistemi unix e quindi questo codice non è portabile su windows. A breve cercherò di proporre una soluzione portabile

09 gennaio, 2007

Python: Udp broadcasting

Oggi voglio riportarvi un esempio di come funziona e come si implementa il broadcast dei pacchetti in python. Prima di iniziare, ricordo a chi non lo sapesse che oltre ai socket TCP (SOCK_STREAM) i quali garantiscono (usando il protocollo tcp) che i dati che vengono trasmessi arrivano correttamente a destinazione, esistono i socket udp (SOCK_DGRAM) i quali non danno nessuna garanzia (esistono anche altri tipi di socket di cui non ci preoccuperemo in questo post). I socket tcp non permettono il broadcast in quanto sono socket basati sulla connessione. Quelli udp invece consentono il broadcast perché sono connection-less. Vi riporto ora un esempio davvero semplice di un broadcast server e di un client che riceve informazioni dal server.

#server

PORT = 50000

import sys, time
from socket import *

s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', 0))
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)

while 1:
try:
data = repr(time.time())
# non funziona se non c'e' un default
# routing

#s.sendto(data, ('<broadcast>', PORT))

# se invece specifico un indirizzo di
# broadcast funziona
s.sendto(data, ('192.168.0.255', PORT))

time.sleep(2)
except KeyboardInterrupt:
sys.exit(0)


Come potete vedere il codice del server è molto banale. In pratica non fa altro che mandare in broadcast ogni 2 secondi il tempo misurato dal sistema. Notiamo che stiamo usando i datagram sockets e li abbiamo messi in modalità broadcast con
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)

A questo punto notate il commento sull'istruzione che usa <> come indirizzo al quale inviare i dati. Sul manuale di python questo non è specificato ma è così. Se vogliamo che i dati vengano ricevuti dal client dobbiamo specificare l'indirizzo di broadcast corretto.
Vediamo ora il codice del client:

import sys
from socket import *

PORT = 50000

s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', PORT))
while 1:
try:
data, fromaddr = s.recvfrom(1024)
print (data, fromaddr)
except KeyboardInterrupt:
sys.exit(0)
Come possiamo notare il client non conosce a priori l'indirizzo del server ma riceve semplicemente i dati. Ricordiamoci comunque che il protocollo udp non garantisce nulla sulla consegna degli stessi. Quindi è possibile che ciò che invia il server non arrivi correttamente a destinazione

08 gennaio, 2007

Pygtk: Un widget in una TextView

Se vi è capitato di imbattervi nel problema di dover inserire una immagine o qualsiasi altro widget in una TextView e non sapete come fare, di seguito vi proporrò una soluzione. Il principio è semplice:

  • troviamo il punto in cui vogliamo inserire il widget e creiamo un TextIter
  • creiamo un' ancora al punto in cui si trova l'iter
  • ancoriamo il widget
Nell'esempio che riporterò, per semplicità considererò come iter la fine del buffer associato alla TextView.

import gtk

tv = gtk.TextView()
buf = tv.get_buffer()
iter = buf.get_end_iter()
anchor = buf.create_child_anchor(iter)

img = gtk.Image()
# sostituire con un path ad un file immagine
# esistente
img.set_from_file("path_del_file")
img.show()

tv.add_child_at_anchor(img, anchor)

tv.show()
sw = gtk.ScrolledWindow()
sw.add(tv)
sw.show()
frame = gtk.Frame()
frame.show()
frame.add(sw)
w = gtk.Window()
w.add(frame)
w.set_default_size(400, 300)
w.show_all()
w.connect("destroy", lambda w: gtk.main_quit())
gtk.main()

La cosa interessante è che in questo caso abbiamo usato una immagine, ma avremmo potuto "caricare" nella text view ad esempio un pulsante o un altro widget

04 gennaio, 2007

Una text view più funzionale

Stavo cercando in rete una text view per il progetto Planimo che fosse un po più funzionale di quella fornita di default con le pygtk. In particolare dovrebbe supportare in parte l'html e magari la possibilità di inserire immagini in modo semplice. Dovrebbe essere possibile ad esempio passare da uno stile per il testo all'altro utilizzando i normali tag html, senza dover scendere a "basso" livello per occuparsi di iteratori e TextTag. Purtroppo ho trovato poco e quindi mi sa che mi tocca scrivermene una da me. Non dovrebbe essere troppo complesso. Cercherò di scriverla in modo che possa essere facilmente riutilizzabile e se otterrò un risultato decente la pubblicherò su questo blog!

Comunque credo che un widget di questo tipo potrebbe far comodo a molti e potrebbe essere il caso che gli sviluppatori delle gtk ne pensassero uno per il kit