Journal Problème de fopen....

Posté par  .
Étiquettes : aucune
0
4
juin
2004
Mon cher journal,
il y a bien longtemps que je n'ai pas écrit de programme en C, aussi ai-je besoin de tes conseils éclairés et avisés.

Voici mon problème :
nous disposons d'une imprimante à aiguilles qui nous sert plusieurs fois par semaine pour effectuer de grosses éditions, il est donc hors de question d'envisager de nous en séparer.
Puisque nous sommes passés sous Linux, nous utilisons CUPS qui fait fonctionner sans broncher nos 50 imprimantes réseaux.... Mais cette antiquité est loin de comprendre le langage hp/gl ou ps level 2 ou 3, loin s'en faut.

Pour pallier ce problème, j'ai donc connecté notre vieille berta au port parallèle de notre serveur, et configuré une file en mode 'raw' sous cups spécialement pour elle.

Comme elle est en mode raw, pas moyen d'envoyer des paramètres pour l'édition à lancer depuis la ligne de commande (lp ou lpr). Voici donc le noeud du problème :
Il m'est absolument impossible de modifier les fichiers textes que nous envoyons à cette imprimante, j'ai donc codé un petit programme en C qui effectue dans l'ordre les tâches suivantes :
- ouverture du fichier /dev/lp0 en écriture
- écriture dans ce "fichier" des caractères de contrôles correspondants aux paramètres de l'édition
- fermeture du fichier /dev/lp0
-Appel système de la commande lp avec pour paramètres le nom de cette file définie en mode raw
- Ouverture du fichier /dev/lp0
- Ecriture du caractère Ctrl-L (pour sauter une page)
- Fermeture de ce foutu fichier

Je dis foutu, car comme la commande lp est lancée depuis un appel à system(), mon petit prog en C ne peut d'aucune manière savoir que CUPS a fini son travail sur le périphérique lp0 et donc mon second appel à fopen échoue avec un beau core-dumped !

Comment puis-je coder un truc un tant soit peu élégant qui attende gentiment que le périphérique /dev/lp0 puisse être ouvert en écriture ?

J'avais bien pensé à :
while (f=fopen("/dev/lp0", "wb+") == null);
mais je trouve ça gore, non ?
On m'a soufflé dans l'oreille de faire un appel à sleep () juste après l'appel à system(), mais j'ai déjà eu des éditions qui ont duré lus de 12 Heures... sans commentaires !
Donc ma question, c'est comment coder de manière élégante ce truc là.
Merci mon cher journal d'éclairer ma lanterne sur ce point !
  • # Ne pas utiliser system, mais fork+exec+handler de signal

    Posté par  . Évalué à 2.

    La solution la plus simple peut etre est de ne pas utiliser system, mais a la place de faire
    - fork
    - exec
    pour remplacer l'appel a system(), et la tu vas me dire oui mais c'est quoi l'interet ? Et bien avec l'appel a fork, tu recuperes le pid de ton processus fils, et ensuite tu attend que ton processus fils soit mort (fin de lp).
    Pour faire ca, il faut si je me rappelle bien enregistrer un handler de signal.
  • # Alors la je repond rapide..

    Posté par  . Évalué à 2.

    car je pars en week-end.
    Cerveau deja plus trop au boulot donc voila grosso modo une piste en pseudo algo et C.

    fin=false;
    while(fin!=true)
    {
    sleep(100);
    system("lpstat -p >/home/toto.log");
    fopen("/home/toto.log");
    je_read_le_nom( de mon imprimante et sont status);
    fclose();
    elle est ready ?
    fin=true;

    }

    le sleep 100 sert a ne pas consommer de ressource cpu pour rien
  • # popen?

    Posté par  (site web personnel) . Évalué à 2.

    sleep() c'est très bien... c'est clean.

    retry:
    if ((f = fopen("/dev/lp0", "wb")) == NULL) {
    sleep(5);
    goto retry;
    }
    /* continuer */
    • [^] # Re: popen?

      Posté par  . Évalué à 1.

      sleep() c'est très bien... c'est clean.
      vrai
      mais le goto me fais vomir en C
      alors que simplement
      while((f = fopen("/dev/lp0", "wb")) == NULL)
      sleep(5);
      aurait suffit
      • [^] # Re: popen?

        Posté par  . Évalué à 3.

        Oui euh non.

        Le sleep() est une commande a banir. (utile dans tres tres peu de cas en fait)
        Faire un sleep, c'est faire un poll et faire un poll c'est prendre du CPU, c'est mal.
        Quitte a essayer de lire un descripteur de fichier, autant faire un select().
        • [^] # Re: popen?

          Posté par  . Évalué à 2.

          Tu peux developper ?
          • [^] # Re: popen?

            Posté par  . Évalué à 1.

            man select :-)

            et "info libc" node "Waiting for I/O"
            • [^] # Re: popen?

              Posté par  . Évalué à 2.

              non non non je parlais du sleep() ca prend du temps CPU :-)
              • [^] # Re: popen?

                Posté par  . Évalué à 1.

                Oui ça prend du temps. Mais pas grand chose.
                Puis une foi que tu as fait le sleep() il faut refaire un (ou des) read() pour voir s'il y a des données. Et là ça prend aussi du temp.
                Avec select(), ton programme est "réveillé" lorsqu'il y a quelque chose à faire et tu sais quel descripteur de fichier a des données.
                Puis c'est plus propre. Lorsqu'on lit le code, on comprend ce que tout de suite l'intention.
                • [^] # Re: popen?

                  Posté par  . Évalué à 1.

                  btw, sleep() utilise les signaux. C'est pas remarquablement rapide.
                  Mais pour ton applis, c'est pas bien grave et pour être honnète je crois que tu vas rien remarquer :-)
                  • [^] # Re: popen?

                    Posté par  . Évalué à 2.

                    Non, sleep() utilise les signaux (un alarm) si nanosleep n'a pas disponible en syscall. Tout les systèmes potables implémentent nanosleep().

                    Au pire usleep utilise un select().

                    Un signal c'est pas très rapide mais pas très lent non plus, mettre un bit a 1 c'est encore honnete (bon j'abuse la :-).
                    • [^] # Re: popen?

                      Posté par  . Évalué à 1.

                      > Non, sleep() utilise les signaux (un alarm) si nanosleep n'a pas disponible en syscall.

                      Non non. Voir :
                      http://linuxfr.org/comments/424791,1.html(...)

                      Il y a appel à sigprocmask() et sigaction().

                      > mettre un bit a 1 c'est encore honnete (bon j'abuse la :-).

                      C'est pas de mettre à 1 qui prend du temp. C'est le passage mode user à noyau et le retour.

                      Puis, on chipote. Par contre entre un select() et un (while(1) { sleep() ; if (read()) ...} ) il n'y a pas photo. La méthode sleep(), c'est 3 fois rien mais la méthode select() c'est 1 centième de 3 fois rien :-)
                      • [^] # Re: popen?

                        Posté par  . Évalué à 2.

                        Tu aurais lu le code dont je t'ai passé l'URL tu aurais vu

                        /* We are going to use the `nanosleep' syscall of the kernel. But the
                        kernel does not implement the stupid SysV SIGCHLD vs. SIG_IGN
                        behaviour for this syscall. Therefore we have to emulate it here. */

                        Les signaux ne sont la que par ce que le noyau fait sa tembouille. Dans l'absolu si nanosleep est dispo y'a aucune raison d'avoir des signaux qui se baladent (cf les libc BSD). Après oui sous _linux_ tu as besoin de faire mumuse avec les sig*() mais ca n'a rien d'inherent à sleep().
                        • [^] # Re: popen?

                          Posté par  . Évalué à 1.

                          Si tu veux...

                          Mais explique moi comment fait un programme sous freebsd pour faire une "pause" sans faire d'appels système vers le noyau ?
                          Comment fait un programme pour se "réveiller" si ce n'est pas le noyau qui le fait ?
                          Comment demander au noyau de "réveiller" un programme dans x secondes si tu ne fais pas d'appel système ?

                          J'aimerai connaitre cette magie...

                          Avec select(), il y a un appel noyau, puis le noyau "réveille" le programme si nécessaire. C'est tout. Si rien arrive durant 3 mois de suite, le programme ne va rien faire.
                • [^] # Re: popen?

                  Posté par  . Évalué à 2.

                  Hum désolé j'avais lu que sleep prennait du temps ce qui au vu du code de la libc me surprenait un tantinet. Mauvaise interpretation de la phrase.
        • [^] # Re: popen?

          Posté par  (site web personnel) . Évalué à 2.

          sleep est implémenté sans consommer de CPU.

          [gc@meuh /tmp] echo "main() { sleep(2); }" > t.c
          [gc@meuh /tmp] gcc -static[1] t.c
          [gc@meuh /tmp] strace ./a.out
          execve("./a.out", ["./a.out"], [/* 59 vars */]) = 0
          uname({sys="Linux", node="meuh", ...}) = 0
          brk(0) = 0x80aa000
          brk(0x80cb000) = 0x80cb000
          open("/dev/urandom", O_RDONLY) = 3
          read(3, "\216R5\334$n\262\343\324\345r\177\302\224\254\373!\242"..., 32) = 32
          close(3) = 0
          rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
          rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0
          rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
          nanosleep({2, 0}, {2, 0}) = 0
          exit_group(0) = ?

          Il bloque deux secondes dans l'appel système nanosleep, qui ne consomme bien sûr aucun CPU.

          [1] -static afin que strace génère moins de bruit dû à ld et la recherche des shlibs
          • [^] # Re: popen?

            Posté par  . Évalué à 1.

            > qui ne consomme bien sûr aucun CPU.
            Mais il y a :
            rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
            rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0
            rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
            nanosleep({2, 0}, {2, 0}) = 0


            Fix les signaux puis un signal est envoyé par le noyau pour arrêter le nanosleep. Il y a plusieur aller/retour userland/noyau. Comme après il fait des read()/write() qui dans la majorité des cas vont retourner 0...

            Certe, je chipote. Mais on est loin du "ne consomme bien sûr aucun CPU".
            • [^] # Re: popen?

              Posté par  . Évalué à 2.

              Ne prend pas non plus le cas de la glibc pour un generalité

              glibc
              http://sources.redhat.com/cgi-bin/cvsweb.cgi/libc/sysdeps/unix/sysv(...)

              FreeBSD libc
              http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/gen/sleep.c?rev=(...)

              Enfin bref c'etait pas le sujet en fait :-)
            • [^] # Re: popen?

              Posté par  (site web personnel) . Évalué à 3.

              Ok - c'est plus du chipotage c'est de l'enculage de mouche à ce niveau-là :) pour être cohérent tu dois banir tout langage autre que l'assembleur et le C alors ? (voire le caml) parce que question appels de bibliothèque et de syscalls les langages interprétés c'est la joie dans les chaumières.

              [gc@meuh /tmp] echo 'main() { printf("hello world\n"); }' > t.c
              [gc@meuh /tmp] gcc t.c
              [gc@meuh /tmp] strace -o c.out ./a.out
              hello world

              [gc@meuh /tmp] strace -f -o bash.out bash -c 'echo "hello world"'
              hello world

              [gc@meuh /tmp] strace -f -o python.out python -c 'print "hello world"'
              hello world

              [gc@meuh /tmp] strace -f -o perl.out perl -e 'print "hello world\n"'
              hello world

              [gc@meuh /tmp] echo 'public class t { public static void main( String args[] ) { System.out.println("Hello world"); } }' > t.java
              [gc@meuh /tmp] javac t.java
              [gc@meuh /tmp] strace -f -o java.out java t
              Hello world

              [gc@meuh /tmp] echo 'print_string "Hello World\n"' > t.ml
              [gc@meuh /tmp] strace -f -o ocaml.out ocaml t.ml
              Hello World

              [gc@meuh /tmp] ocamlc t.ml
              [gc@meuh /tmp] strace -f -o ocamlc.out ./a.out
              Hello World

              [gc@meuh /tmp] wc -l *.out | sort -n
              25 c.out
              81 ocamlc.out
              84 bash.out
              100 perl.out
              159 ocaml.out
              522 python.out
              2990 java.out

              On notera la belle performance de notre ami Java. Et que vraiment Python, par rapport à Perl, ça sux grave.

              Disclémeur : ok je mesure essentiellement le startup là :/
  • # et dans un script ?

    Posté par  . Évalué à 1.

    pour quoi ne pas faire un petit programme qui envoie une suite de caractere a l'imprimante par le biais de /dev/lp0 :
    - ouverture du fichier /dev/lp0 en écriture
    - écriture dans ce "fichier" des caractères (arguments, a lire ds un autre fichier, ...)
    - fermeture du fichier /dev/lp0

    puis un simple script :

    char_send "caractères de contrôles correspondants aux paramètres de l'édition" &&
    commande lp avec pour paramètres le nom de cette file définie en mode raw &&
    char_send "Ctrl-L (pour sauter une page)"

    voir meme pour etre encore plus generaliste, demander de passer le fichier a ouvrir...

    et alors on tombe sur une simple redirection shell :)

    echo "caractères de contrôles correspondants aux paramètres de l'édition" > /dev/lp0 &&
    commande lp avec pour paramètres le nom de cette file définie en mode raw &&
    echo ""Ctrl-L (pour sauter une page)" > /dev/lp0

    ce qui resoud pas mal de pb de gestion des processus...
  • # pourquoi pas un simple script

    Posté par  (site web personnel) . Évalué à 2.

    Pourquoi pas un simple script ? En bash par exemple.

    C'est bien plus bourrin que ton fopen ou que le select proposé ailleurs, mais c'est bien plus court : a priori un simple "echo -n" redirigé n'écrit rien dans la redirection (j'ai vérifié avec strace) mais tente tout de même d'ouvrir la redirection en écriture c'est justement ce qu'il te faut.

    #!/bin/sh
    cat /etc/parametres-vielle-imprimante-pourrie > /dev/lp0
    lp fichier...
    while ! echo -n > /dev/lp0; do
        echo "Attente..."
        sleep 2
    done
    cat /etc/ctrl-l-vielle-imprimante-pourrie > /dev/lp0
    • [^] # Re: pourquoi pas un simple script

      Posté par  . Évalué à 1.

      Oui, j'y ai pensé, j'ai oublié, mais j'y repense... Enfin, je ne sais pas de trop (après tout, je suis normand !)
      Un script... En fait, je voulais me remettre à faire du C, et puis, les caractères de contrôle de l'imprimante doivent être préfixées avec des séquences d'échapement spéciales... Bref, tout un bouzin...
      Je trouvais ça rigolo en C, j'ai même pensé à écrire un "filttre" pour cups... Puis même un daemon pour ma vieille_imprimante_pourrie_konaimebienquandmeme !
      Bref, c'était juste pour comprendre plusieurs choses sur comment fonctionne tout ce micmac !
      C'est vrai quoi ! En cours, on m'a juste appris comment ouvrir/remplirOUvider/fermer un fichier, jamais comment faire pour savoir si quelqu'un l'utilise, et/ou, comment être notifié de la {,non}utilisation de celui-ci.
      Enfin, je dis fichier, mais là, il s'agit d'un périphérique, même si sous Linux/Unix c'est sensiblement la même notion pour ce que j'en sais.
      Il semblerait que l'appel à select soit la solution à mon problème. Faut que je regarde ça de plus près.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.