Rigtigt (server-)program i Perl

Dette forum bruges på EGET ANSVAR til at lege med scripts og andre ting med risiko for at beskadige sit eget og andres systemer.
Brugeravatar
NickyThomassen
Admin
Indlæg: 3650
Tilmeldt: 5. mar 2010, 19:58
IRC nickname: nicky
Geografisk sted: 192.168.20.42

Rigtigt (server-)program i Perl

Indlæg af NickyThomassen »

Hej folkens :)

Som i ved startede jeg virksomhed sidste år, og jeg sidder nu og ligger sidste hånd på mailserveren.

Den har været noget af en udfordring at sætte op, ikke mindst fordi der praktisk taget ikke er nogen "rigtig" måde at gøre det på[1], og så fordi det er svært at få feedback fra venner og familie på sådan et projekt. Det sidste der mangler nu, er et program som ordner de sidste hængepartier, og jeg regner med at køre det som en daemon.

Jeg ville værdsætte alle typer af kommentarer på programmet, da jeg muligvis er ved at stirre mig blind på det. Det er ikke lavet færdigt, men jeg er godt i gang, og har indtil videre lavet / taget højde for følgende ting:

1. Programmet har 2 funktioner: A), at træne spamfilteret ud fra kopier af mails, som mailserveren opretter i /var/spool når en bruger flytter en mail til / fra sin spam-mappe. B), at tage en brugers sortliste[2] og konventere den til en blokeringsliste som mailserveren kan bruge til at afvise mails ud fra.

Jeg overvejede at lave ét program pr. funktion, men endte med at samle det for at holde al vedligeholdelse til mailserveren ét sted, for at undgå konflikter. Ligeledes laver jeg det som program i stedet for som script, for at undgå at crontab starter en ny instans inden den gamle er færdig. Det sidste kan løses med en PID-agtig fil, men alligevel.

2. Programmet kommer til at kunne bruge syslog eller sin egen logfil. Ønsker man begge dele, så må man sætte syslog op til at tage programmets logdata ud af syslog.

3. Programmet får sin konfiguration i /etc/default/ og sit eget init-script med {start|stop|restart|status} og default runlevels. Pt. kommer det kun til at understøtte Debian, men hvis nogen udpeger forskelle imellem Debian og fx Ubuntu, så integrerer jeg dem gerne. Ville github[3] være egnet til sådan noget?

4. Perl-specifikt bruger jeg kun core-moduler, og kun så lidt som muligt. Tanken er at holde afhængighederne så lavt som muligt, selv på bekostning af afviklingshastighed og udviklertimer. Nogen tanker til sådan en tilgang? Dertil bruger jeg warnings og strict for at holde koden så "korrekt" som mulig.

5. Programmet får brug for adgang til filer fra mailserveren, som har rettighederne vmail:vmail 600 og systemfiler som har root:root 644 og sikkert 600. Derfor regner jeg med at køre programmet som root. Udover de mere åbenlyse problemstillinger, er der så mere diskrete problemstillinger? Kan det faktisk betale sig i udviklertid at køre hvad der er muligt uden privilegier?

6. Pt. består programmet dermed af programfilen, init-scriptet og konfigurationsfilen. Hvad er den bedste måde at distribuere og installere det på? Manuelt virker tiltalende for så lidt, men hvis der kommer flere filer til bliver det hurtigt besværligt.

7. Både Perl-specifikt og Linux-specifikt, er der så nogle ting man skal være opmærksomme på med langtidskørende programmer? Jeg ved at Perl bruger meget hukommelse, men helt ærligt sagt, så er jeg ligeglad[4]. Derimod tænker jeg på mere specifikke ting som memory leaks, buffer overflows og den slags, og om der er særlige ting at være opmærksom på / undgå når man laver programmer til Linux.

Tak for at læse så langt :)

---

[1] Når bare den overholder diverse RFCs.
[2] Liste over afsendere i en database, som brugeren har markeret at de ikke ønsker mails fra.
[3] https://github.com/TitanusEramius
[4] Ram idag er ikke ret dyre, og om et program bruger 5 eller 50mb er i min mening underordnet, så længe det er tilsigtet.
Brugeravatar
NickyThomassen
Admin
Indlæg: 3650
Tilmeldt: 5. mar 2010, 19:58
IRC nickname: nicky
Geografisk sted: 192.168.20.42

Re: Rigtigt (server-)program i Perl

Indlæg af NickyThomassen »

Jeg skrev for en måneds tid siden et indlæg på min hjemmeside, som netop beskrev et minimalt baggrundsprogram i Perl. Det her program bliver bygget op omkring de principper:
http://aptget.dk/2013/03/29/baggrundsprogram-i-perl/

Noget jeg altid har prøvet, er at opbygge generelle funktioner, som så kan bruges i andre programmer. I går fik jeg bygget den funktion færdig, som læser, validerer og returnerer indholdet af konfigurationsfilen. Den der sådan her ud:

Kode: Vælg alt

sub parse_config_file {
   # These are all needed for the subroutine, which their scope is.
   my ($config, $config_line, $file, $name, $value);
   # Read the paramteres given to the subroutine.
   ($file, $config) = @_;
   # Open the file in $file.
   (open (CONFIG, "$file")) or die "Configurationfile could not be opened: $!";
   # Reads one line at a time from $file.
   while ( <CONFIG> ) {
      # Read each line from the file.
      $config_line = $_;
      # Remove linebreaks.
      chop ($config_line);
      # Remove midline comments.
      $config_line =~ s/#.*//;
      # Remove spaces at the start of the line.
      $config_line =~ s/^\s*//;
      # Remove spaces at the end of the line.
      $config_line =~ s/\s*$//;
      # Ignore lines starting with # and blank lines.
      if ( ( $config_line !~ /^#/ ) && ( $config_line ne "" ) ) {
         # Split each line into name value pairs.
         ($name, $value) = split (/=/, $config_line);
         # Removes spaces after $name.
         $name =~ s/\s*$//;
         # Removes spaces before $value.
         $value =~ s/^\s*//;
         # Check for spaces within $name.
         if ( $name =~ /\s/ ) {
            die "\n*** Space(s) found in \"$name\" ***\n\n";
         }
         # Check for spaces within $value.
         if ( $value =~ /\s/ ) {
            die "\n*** Space(s) found in \"$value\" ***\n\n";
         }
         # Creates a hash of the name:value pairs.
         $$config{$name} = $value;
      }
   }
   # Close the configurationfile.
   close(CONFIG);
}

Som sagt er den noget længere end hvis jeg brugte et modul til at læse filen med, men sådan her er der ingen afhængigheder, og det er meget tydeligt hvad der sker i funktionen. Funktionen kan helt sikkert forbedres, men sådan her dækker den 90% af hvad jeg har brug for.
lath
Indlæg: 5095
Tilmeldt: 27. apr 2008, 02:16
IRC nickname: lars_t_h
Geografisk sted: Fyn

Re: Rigtigt (server-)program i Perl

Indlæg af lath »

TitanusEramius skrev:Jeg skrev for en måneds tid siden et indlæg på min hjemmeside, som netop beskrev et minimalt baggrundsprogram i Perl. Det her program bliver bygget op omkring de principper:
http://aptget.dk/2013/03/29/baggrundsprogram-i-perl/

Noget jeg altid har prøvet, er at opbygge generelle funktioner, som så kan bruges i andre programmer. I går fik jeg bygget den funktion færdig, som læser, validerer og returnerer indholdet af konfigurationsfilen. Den der sådan her ud:

Kode: Vælg alt

sub parse_config_file {
   # These are all needed for the subroutine, which their scope is.
   my ($config, $config_line, $file, $name, $value);
   # Read the paramteres given to the subroutine.
   ($file, $config) = @_;
   # Open the file in $file.
   (open (CONFIG, "$file")) or die "Configurationfile could not be opened: $!";
   # Reads one line at a time from $file.
   while ( <CONFIG> ) {
      # Read each line from the file.
      $config_line = $_;
      # Remove linebreaks.
      chop ($config_line);
      # Remove midline comments.
      $config_line =~ s/#.*//;
      # Remove spaces at the start of the line.
      $config_line =~ s/^\s*//;
      # Remove spaces at the end of the line.
      $config_line =~ s/\s*$//;
      # Ignore lines starting with # and blank lines.
      if ( ( $config_line !~ /^#/ ) && ( $config_line ne "" ) ) {
         # Split each line into name value pairs.
         ($name, $value) = split (/=/, $config_line);
         # Removes spaces after $name.
         $name =~ s/\s*$//;
         # Removes spaces before $value.
         $value =~ s/^\s*//;
         # Check for spaces within $name.
         if ( $name =~ /\s/ ) {
            die "\n*** Space(s) found in \"$name\" ***\n\n";
         }
         # Check for spaces within $value.
         if ( $value =~ /\s/ ) {
            die "\n*** Space(s) found in \"$value\" ***\n\n";
         }
         # Creates a hash of the name:value pairs.
         $$config{$name} = $value;
      }
   }
   # Close the configurationfile.
   close(CONFIG);
}

Som sagt er den noget længere end hvis jeg brugte et modul til at læse filen med, men sådan her er der ingen afhængigheder, og det er meget tydeligt hvad der sker i funktionen. Funktionen kan helt sikkert forbedres, men sådan her dækker den 90% af hvad jeg har brug for.


Der er en række ting man skal lave i et program for at gøre den til en daemon (det der i Windows terminologi hedder en service). Først når de er sket er opfylder programmet definitionen for at være en daemon.

Noget at tænke over.
Da du bruger et scripting sprog (Perl) ville jeg gøre min daemon til et meget lille/simpelt C, C++ eller Pascal program som kun starter Perl scripts op i en anden process.
Ideen er at hvis der tilfældigvis er en memory leak i Perl, en Perl extension eller din egen Perl kode, så dør memory leaken når kernen indrager scipt-programmets memory som ledigt RAM, når dit script er færdigt med sit arbejde.

Du kunne bruge CRON jobs til at starte med, og hvis det skal være mere avanceret kan du kode din egen daemon.

Noget om hvordan man gør et Linux program til en Linux daemon finder man f.eks. i en bog der hedder "Linux System Programming", og jeg har lige sakset det lille afsnit om Daemons derfra (ss178-180) dog excl koden:
Daemons
A daemon is a process that runs in the background, not connecting to any control-
ling terminal. Daemons are normally started at boot time, are run as root or some
other special user (such as apache or postfix), and handle system-level tasks. As a
convention, the name of a daemon often ends in d (as in crond and sshd), but this is
not required, or even universal.

The name derives from Maxwell’s demon, an 1867 thought experiment by the physi-
cist James Maxwell.

Daemons are also supernatural beings in Greek mythology,
existing somewhere between humans and the gods and gifted with powers and divine
knowledge. Unlike the demons of Judeo-Christian lore, the Greek daemon need not
be evil. Indeed, the daemons of mythology tended to be aides to the gods, performing
tasks that the denizens of Mount Olympus found themselves unwilling to do—much
as Unix daemons perform tasks that foreground users would rather avoid.

A daemon has two general requirements: it must run as a child of init, and it must
not be connected to a terminal.

In general, a program performs the following steps to become a daemon:

1. Call fork( ). This creates a new process, which will become the daemon.

2. In the parent, call exit( ). This ensures that the original parent (the daemon’s
grandparent) is satisfied that its child terminated, that the daemon’s parent is no
longer running, and that the daemon is not a process group leader. This last
point is a requirement for the successful completion of the next step.

3. Call setsid( ), giving the daemon a new process group and session, both of
which have it as leader. This also ensures that the process has no associated con-
trolling terminal (as the process just created a new session, and will not assign
one).

4. Change the working directory to the root directory via chdir( ). This is done
because the inherited working directory can be anywhere on the filesystem. Dae-
mons tend to run for the duration of the system’s uptime, and you don’t want to
keep some random directory open, and thus prevent an administrator from
unmounting the filesystem containing that directory.

5. Close all file descriptors. You do not want to inherit open file descriptors, and,
unaware, hold them open.

6. Open file descriptors 0, 1, and 2 (standard in, standard out, and standard error)
and redirect them to /dev/null.

Following these rules, here is a program that daemonizes itself ...


en guide til daemonizing en Linux process findes her: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html, her: http://codingfreak.blogspot.com/2012/03/daemon-izing-process-in-linux.html, og her: http://wordptr.com/2011/01/31/lets-write-a-linux-daemon-part-i/

/Lars
Jeg er Software ingeniør (Diplomingeniør) i Informationsteknologi og indlejede systemer, hvor indlejrede systemer er computer (microcontroller) + elektronik i for eksempel et TV, en router, en vaskemaskine og den slags
Brugeravatar
NickyThomassen
Admin
Indlæg: 3650
Tilmeldt: 5. mar 2010, 19:58
IRC nickname: nicky
Geografisk sted: 192.168.20.42

Re: Rigtigt (server-)program i Perl

Indlæg af NickyThomassen »

Mange tak for svaret, omend jeg må indrømme at jeg ikke lærte noget nyt :)

Fik du kigget på http://aptget.dk/2013/03/29/baggrundsprogram-i-perl/ ?
Med det gennemfører jeg det samme som eksemplerne i C gør det - bare med Perl. Det program jeg linker til laver ikke andet end at skrive en linie til loggen hvert 3 sekund, og det fylder 2300kb i RAM.

Selvfølgelig ville et C program fylde en del mindre i RAM, men på en moderne mailserver gør det ikke den store forskel mere. SpamAssassin fylder fx 65mb pr. child, og der er reelt set ingen open source alternativer.

Udover at jeg ikke ret godt kan C, så virker Perl til at være meget hurtigere at udvikle, men du har ret i at en form for "master process" ville være en god idé. Uden andre opgaver end at styre diverse childs, kunne man sikkert opnå en meget høj stabilitet. Postfix er iøvrigt lavet sådan - og i C.
lath
Indlæg: 5095
Tilmeldt: 27. apr 2008, 02:16
IRC nickname: lars_t_h
Geografisk sted: Fyn

Re: Rigtigt (server-)program i Perl

Indlæg af lath »

TitanusEramius skrev:Mange tak for svaret, omend jeg må indrømme at jeg ikke lærte noget nyt :)

Fik du kigget på http://aptget.dk/2013/03/29/baggrundsprogram-i-perl/ ?
Med det gennemfører jeg det samme som eksemplerne i C gør det - bare med Perl. Det program jeg linker til laver ikke andet end at skrive en linie til loggen hvert 3 sekund, og det fylder 2300kb i RAM.

Selvfølgelig ville et C program fylde en del mindre i RAM, men på en moderne mailserver gør det ikke den store forskel mere. SpamAssassin fylder fx 65mb pr. child, og der er reelt set ingen open source alternativer.

Udover at jeg ikke ret godt kan C, så virker Perl til at være meget hurtigere at udvikle, men du har ret i at en form for "master process" ville være en god idé. Uden andre opgaver end at styre diverse childs, kunne man sikkert opnå en meget høj stabilitet. Postfix er iøvrigt lavet sådan - og i C.


Du gør ikke det samme - du har glemt at fork(2)'e. I perl kan du - såvidt jeg lige husker det - godt kalde fork direkte, ellers kunne jeg muligvis på ikke så lang tid skrue noget C kode sammen for dig, som execv(3) -> wait(2) kalder perl fortolkeren.
Derfra er der ikke langt til at laveen daemon som i en while(1) uendelig løkke kører en fork->exec->wait sekvens, som kalder et perl master script med jævne mellemrumved at den sover, dvs sleep(3)'er, i et passende tidsrum efter wait.
Hvad den skal gøre og hvilke argumenter den skal bruge kan man smide i en konfigurationsfil, og så lade daemonen fange SIGHUB singalet så det ikke dør, men i stedet genindlæser konfigurationsfilen. SIGHUB laves med en:

Kode: Vælg alt

kill $(pgrep min_daemon)


Med f.eks. fork(2), referer jeg til:

Kode: Vælg alt

man 2 fork

Kode: Vælg alt

while(1)

... er C kode.

off-topic: Mega-skummelt tordenvejr er i anmarch fra syd (Tyskland) lige nu - jeg kan høre tordenbrag.

/Lars
Jeg er Software ingeniør (Diplomingeniør) i Informationsteknologi og indlejede systemer, hvor indlejrede systemer er computer (microcontroller) + elektronik i for eksempel et TV, en router, en vaskemaskine og den slags
Brugeravatar
NickyThomassen
Admin
Indlæg: 3650
Tilmeldt: 5. mar 2010, 19:58
IRC nickname: nicky
Geografisk sted: 192.168.20.42

Re: Rigtigt (server-)program i Perl

Indlæg af NickyThomassen »

Jo, på linie 28 i den linkede artikel:

Kode: Vælg alt

defined($pid = fork) or die "Fatal: Can not fork: $!\n";

fork() bliver kaldt, og hvis $pid ikke har indhold efter kaldet, så skal programmet afslutte.
De to () er ikke nødvendige i koden for indbyggede funktioner som fork(), så de kan udelades.

Jeg tror faktisk at vi er ved at få det samme vejr heroppe i Nordsjælland, for det har trukket op hele eftermiddagen. Det ligner efterhånden regulære tordenskyer.
lath
Indlæg: 5095
Tilmeldt: 27. apr 2008, 02:16
IRC nickname: lars_t_h
Geografisk sted: Fyn

Re: Rigtigt (server-)program i Perl

Indlæg af lath »

mcrypto skrev:...
isaer er each en hopla function for os linux UBUNTU brugere, fordi den each'er.
....

Det er bare en løkke-konstruktion - ikke noget specielt. Den findes i (næsten) alle programmeringssprog, men hvis man på den anden siden er helt ny ud i programmeringssprog, så bliver man meget hurtigt glad for den :)

Feks. i C:

Kode: Vælg alt

for(int i = 0; i < max; i++ {
//her inde køres der i løkke sålænge i < max eller man breaker ud af løkken.
}


I Go, http://golang.org/ :

Kode: Vælg alt

import "fmt"
strs := []string{"abc", "def", "ghi"}
for _, s := range strs {
fmt.Println(s)
}

Overnævnte Go kode skal pakkes ind i en main funktion, så man har et Go program.
Ved at klikke på det her link kan du besøge en play.golang.org web side, hvor du kan køre koden på Google server farm ved at trykke på "Run" knappen på den web side: http://play.golang.org/p/2VZP8DjEq4

/Lars
Jeg er Software ingeniør (Diplomingeniør) i Informationsteknologi og indlejede systemer, hvor indlejrede systemer er computer (microcontroller) + elektronik i for eksempel et TV, en router, en vaskemaskine og den slags
lath
Indlæg: 5095
Tilmeldt: 27. apr 2008, 02:16
IRC nickname: lars_t_h
Geografisk sted: Fyn

Re: Rigtigt (server-)program i Perl

Indlæg af lath »

mcrypto skrev:Each er en loop construct? Godt så. Men hvilke funktioner i std c modsvarer den?

Ingen!
De er en del af C sprogets syntax.

Den viste for konstruktion i mit tidligere indlæg, while konstruktionen, og så do-while konstruktionen.

Alle kan de løkke-konstruktioner kan skrives om til en if-sætning og så en goto-sætning med tilhørende label, hvilket præcis er hvad de bliver oversat til, når en oversætter/compiler skal skrive maskinkode.

/Lars
Jeg er Software ingeniør (Diplomingeniør) i Informationsteknologi og indlejede systemer, hvor indlejrede systemer er computer (microcontroller) + elektronik i for eksempel et TV, en router, en vaskemaskine og den slags