TP Accès au système



Introduction

Rappel : de façon générale, un processus est un programme en cours d'exécution, avec son propre environnement d'exécution fourni par le système, et un quota de temps processeur.
Si les exemples de ce chapitre sont adaptés à un système Linux, le lecteur transposera sans peine à son OS favori.
Pour mieux connaitre les processus sous Linux, on peut commencer par ceci
Pour les problèmes de sécurité : http://www.enstimac.fr/Perl/DocFr/perlsec.html

Passer des commandes au système avec l'opérateur backquote `

Les commandes system, exec et fork

Communiquer entre processus par tube (pipe)

  1. Un tube est un canal de communication permettant de joindre directement la sortie d'un processus et l'entrée d'un autre.
  2. Exemple classique : ps aux | grep http, la sortie de la commande ps, par défaut dirigée vars l'écran est détournée vers l'entrée de la commande grep pour rechercher les lignes où se trouve la chaine "http"
  3.  Récupérer en lecture la sortie produite par un autre programme
    open F, "commande |";
    Exemple : de nouveau interroger la commande who
    #!/usr/bin/perl -w
    # who.pl pour tester le pipe en entrée
    ######################################
    open W, "who |";
    @w = ;
    chomp @w;
    foreach $i (2..@w-1) {
    print "$w[$i]\n";
    }
    

  4.  Envoyer la sortie du script en entrée d'un autre programme
    open F, "| commande ";
    Exemple : envoyer un message par script Perl
    #!/usr/bin/perl
    # essai-mail.pl
    # attention : le code devra etre adapté
    #######################################
    print "Essai envoi de mail par Perl\n";
    open(MAIL,"|/usr/lib/sendmail -t") or die "impossible de lancer sendmail";
    #!/usr/bin/perl
    print "Essai envoi de mail par Perl\n";
    open(MAIL,"|/usr/lib/sendmail -t") or die "impossible";
    
    # attention : dans une chaine, le caractère @ doit etre protégé par \
    $message="From: Jean <jgourdin\@ac-creteil.fr>
    To: Toto <jules.toto\@ac-creteil.fr>
    Subject: Bonjour Toto !
    Si tu reçois ce message, réponds moi vite !
    
    Jean";
    print MAIL $message;
    close MAIL;
    

Accès au système de fichiers

fonction Perl signification
rename() renomme ou déplace un fichier
unlink() efface un fichier
symlink(,) crée un lien symbolique (ln -s)
chdir() change de répertoire
mkdir() crée un nouveau répertoire
rmdir() efface un répertoire
$rep = "/home";
opendir R, $rep;
associe un descripteur à un répertoire.
Si $rep est ".", on ouvre le rép. courant
@liste=readdir(R); retourne la liste du répertoire
closedir R; fermer la lecture du répertoire
Exemple à tester
Lister le répertoire passé en paramètre
#!/usr/bin/perl -w
# obtenir la liste du répertoire passé en paramètre, 
# à défaut le rép courant print "Entrer un chemin de répertoire (par défaut /home\n"; $rep =<>; chomp($rep); $rep= "." if ! $rep; chdir($rep); print "$rep\n"; opendir R, $rep or die "impossible d'ouvrir le rép. $rep"; @liste=readdir(R); foreach $f (@liste) { next if $f eq "." or $f eq ".."; if (-f $f) { print " |--".$f." "x(30-length($f))."-"; print "r" if (-r $f); print "w" if (-w $f); print "x" if (-x $f); print "\t"; print -s $f; print "\n"; } else { print " d $f\n"; } } closedir R;

 TP

############ Quelques appels de commandes système ##############

Expliquer et tester
Remarque : les commandes shell, en particulier echo, envoie un saut de ligne à la fin, donc inutile dans ce contexte d'ajouter des "\n".
---------------------------------------------
# moyen compliqué d'effacer l'écran !
$ perl -e 'system "clear" ';
# afficher la date et l'heure système
$ perl -e 'print `date` ';
# détourner la sortie des commandes vers un fichier
$ perl -e '`date;cal;pwd;w > commandes.txt` ';
$ cat commandes.txt

############### Créer des comptes ################

Pour réaliser un script général permettant de créer des comptes Linux, éventuellement Samba, il est nécessaire de bien connaitre la commande useradd (on peut consulter pour une introduction )
---------------------------------------------
#!/usr/bin/perl -w
# appel : compte.pl, pour créer un compte
# avec les paramètres par défaut
#########################################

print "Donner un login : ";
$login =  <STDIN>;
chomp $login;
# sortir du script si la chaine $login est vide
exit if ....;
print "Création du compte $login\n";
`useradd $login`;
# tester l'existence du répertoire normalement créé /home/$login/
print "le compte $login est créé\n" if ........;
# récupérer la liste des champs de la nouvelle ligne créée dans /etc/passwd
# utiliser de préférence la commande système grep si on la connait
# sinon, on peut parcourir le fichier /etc/passwd et filtrer les lignes avec $login
@login = split /:/, ..............;
print "son numéro uid est $login[2]\n";
Prolongement :

############# Suivi des processus d'un système UNIX ##############

On demande de tester, d'expliquer les résultats et de prolonger les exemples qui suivent
Quelques informations indispensables :
---------------------------------------------------------------
# au niveau de la ligne de commande système, on fait les présentations
# vérifier la nature de ces 2 PID, avec la commande système usuelle ps aux
$ echo $$
$ echo Je suis le processus $$; echo Créé par $PPID

# on lance 3 fois de suite un processus "script Perl"
# que remarquez vous ? expliquez !
$ perl -e 'print $$, "Bonjour\n" ';
$ perl -e 'print $$, "Bonjour\n" ';
$ perl -e 'print $$, "Bonjour\n" ';

# expliquez cette histoire de famille !
$ echo Je suis le processus $$; echo Créé par $PPID
$ perl -e 'print "Je suis le processus $$ \n Créé par", getppid,"\n";

# lancement dans un script Perl d'une commande système, par exemple `pwd`
# pourquoi obtient-on les mêmes PID avant et après ?
$ echo $$
$ perl -e 'print $$, "\n", `pwd`, $$, "\n" ';
$ echo $$

############ Savez-vous "forker" #############

On demande de comprendre l'exemple suivant qui montre que les 2 processus individuellement différents (le "père" et le "fils") exécutent le même code simultanément grâce au multitâche
#! /usr/bin/perl -w
# fork0.pl, juste pour apprendre à "forker"
print "Ici le père : mon PID est $$, celui de mon propre père est " . getppid . "\n";
print "Attention, je vais \"forker\" ...\n\n";
$pid = fork;
die "Je ne peux pas forker !" if ! defined ($pid);
if ($pid == 0) {
  print "Pour moi \$pid = $pid, donc je suis le processus fils $$, ";
  print "mon père est le processus ". getppid ."\n\n";
} else {
  print "Pour moi \$pid = $pid, donc je suis le processus père $$, ";
  print "mon fils est le processus $pid \n";
}

################## Plus loin avec fork ##############

Voici 2 autres scripts mettant en valeur le mécanisme fondamental de la bifurcation en Unix
  1. fork1.pl montre bien le déroulement de l'appel système exec. En particulier remarquer l'avertissement du compilateur Perl avant l'exécution du script. La commande waitpid($pid,0), impose au processus courant d'attendre que le processus $pid (ici le "fils") se termine
    #! /usr/bin/perl -w
    # fork1.pl #
    print "Ici le père : mon PID est $$, celui de mon propre père est " . getppid . "\n";
    print "Attention, je vais \"forker\" ...\n\n";
    $pid = fork;
    die "Je ne peux pas forker !" if ! defined ($pid);
    if ($pid == 0) {
      print "Pour moi \$pid = $pid, donc je suis le processus fils $$, ";
      print "mon père est le processus ". getppid ."\n\n";
    
      # petite vérification avec la commande ps
      print `ps`;
      print "Je vais exécuter la commande \"exec date\" \npuis quand j'ai fini, je disparais\n\n ...";
      exec "date";
    
      ####################################################################
      # Les 2 instructions SUIVANTES NE SERONT PAS EXECUTEES, POURQUOI ? #
      ####################################################################
      print `ps`;
      print "Père, ai-je bien travaillé ?\n";
    } else {
      print "Pour moi \$pid = $pid, donc je suis le processus père $$, ";
      print "mon fils est le processus $pid \n";
      waitpid( $pid, 0);
      print "Et je viens d'attendre, avec \"waitpid($pid,0)\", que mon fils $pid ait terminé son boulot !\n";
      print "Vérifions avec la commande ps \n",`ps`;
    }
    
  2. fork2.pl montre comment on peut modifier les priorités du système vis à vis des processus
    #! /usr/bin/perl -w
    # fork2.pl : il s'agit de comparer le travail du père et du fils,
    # et de montrer comment donner une priorité à l'un des processus.
    #################################################################
    print "Ici le père \$pid = $$, et je vais \"forker\" ...\n\n";
    $pid = fork;
    die "Je ne peux pas forker !" if ! defined ($pid);
    
    ####################################################################################################
    # on agit sur les priorités de chaque processus avec la fonction setpriority                       #
    # setpriority 0, 0, getpriority(0,0)+10 signifie ajuster la priorité d'un processus (1er 0),       #
    # le processus courant (2ème 0) et lui donner la priorité ancienne getpriority(0,0) augmenté de 10,#
    # ce qui indique une "gentillesse" du fils augmenté de 10 (le fils laisse le CPU au père ...)      #
    ####################################################################################################
    if ($pid == 0) {
      print "Pour moi \$pid = $pid, donc je suis le processus fils $$\n\n";
      setpriority 0, 0, getpriority(0,0)+10 ;
    }
    # Voici un travail de comptage à effectuer pour le père ET pour le fils
    for  ($i=0; $i <10;$i ++) {
      $k=0;
      for  ($j=0; $ < 1000000;$j ++) {
       $k++;
      # le processus en cours d'exécution affiche maintenant son PID
      }
      print "$$\n";
    }
    # Le père a bien sur fini le premier. Il attend quand même que son fils ait fini !
    waitpid( $pid, 0) if $pid !=0 ;
    

########### Connaitre les serveurs hébergés par le système ###########

Ecrire le script lister-services.pl
#!/usr/bin/perl -w
##########################################
# lister-services.pl, liste des services #
# pour un niveau passé en parametre      #
##########################################

print "Donner un niveau de demarrage (par defaut 3) : ";
$niveau= ...
chomp ...
$niveau=3 if ...

print "Services installés au niveau $niveau de démarrage système\n";
# donné uniquement pour ceux qui ne connaissent pas Linux (ou system V) ;-)
opendir N, "/etc/rc".$niveau.".d/";
foreach $service ......... {
# pour éliminer les débuts de ligne (du genre S45 ou K99)
# et récupérer ainsi les noms des scripts de gestion
$service =~ s/.../.../ ;
print "$service \n ";
}
close N;

################## Ne pas oublier les anniversaires ! #################

Il s'agit de faire afficher par le système chaque jour, uniquement lors de son premier démarrage (ou bien à chaque ouverture de session) par exemple la liste des noms des personnes dont c'est le jour anniversaire, avec un message d'encouragement !
  1. Appel anniversaire.pl [date], le paramètre date étant facultatif
    Créer un fichier anniversaire.txt contenant sur chaque ligne une date (au format 25/11 ou autre) suivie d'une liste de noms ou de prénoms.
    Bien sur, on peut aussi envisager la consultation d'une table d'une base de données
  2. Récupérer sous forme de hachage cette correspondance
  3. Utiliser la date système (commande date), seulement si il n'y a pas de paramètre date sur la ligne de commande
  4. Afficher un message personnalisé, pour chaque personne, si la date est trouvée comme clé du hachage

############### Connaitre les signaux système ###########

Tester signaux.pl
#!/usr/bin/perl -w
#####################################################
# signaux.pl, pour connaitre les signaux du système #
#####################################################
use Config;
unless ($Config{sig_name} && $Config{sig_num}) {
die "pas de signaux sur ce système !";
}
# Le module Config fournit les 2 listes suivantes :
# $Config{sig_name} est la liste des noms symboliques,
# $Config{sig_num} est la liste des numéros de signaux
@noms = split ' ', $Config{sig_name};
@nums  = split ' ', $Config{sig_num};

while (@noms) {
# construction d'une liste des signaux indicées avec leur numéro
  $nom= pop @noms;
  $num = pop @nums;
  $signaux[$num]=$nom;
}
 for ($i=0;$i < @signaux;$i++) {
  print "$i --> $signaux[$i]\t ";
  print "\n" if $i % 5 ==0 ;
}
print "\n";