Newsgroup: it.comp.java
Titolo: tcsetattr interrompe esecuzuone di applicazione java con metodo nativo

Ho scoperto che l'inserimento della riga
tcsetattr(STDIN_FILENO,TCSANOW,&oldt);
in un programma in C che implementa un metodo nativo di un'applicazione
java causa l'interruzione prematura di un'applicazione java eseguita con
java nomeClasse &
mentre tutto fila liscio con
java nomeClasse
E' possibile evitare ciò?
Segue codice
----------------------------------------------------
/* file Test.java */
public class Test
  {
   public native int test();
   public static void main(String[]args)
    {
     System.load((new java.io.File("libtest.so")).getAbsolutePath());
     new Test();
    }
   public Test()
    {
     System.out.println("Inizio test");
     System.out.println("test()="+test());
     System.out.println("Fine test");
    }
  }
----------------------------------------------------
/* file test.c */
  #include <jni.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <ncurses.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <signal.h>
#include "Test.h"
static struct termios oldt;
void inizio()
  {tcgetattr(STDIN_FILENO,&oldt);}
void fine()
  {tcsetattr(STDIN_FILENO,TCSANOW,&oldt);}
JNIEXPORT jint JNICALL Java_Test_test (JNIEnv * env,jobject obj)
  {
   printf("Inizio metodo nativo\n");
   inizio();
   fine();
   printf("Fine metodo nativo\n");
   return 0;
  }
----------------------------------------------------
/* compilazione [dal path dei files Test.java e test.c] */
JAVA="/usr/lib/jvm/java-1.6.0-openjdk" # adattare all'effettivo percorso
$JAVA/bin/javac Test.java
$JAVA/bin/javah Test
gcc -I $JAVA/include -I $JAVA/include/linux -shared test.c -o libtest.so
----------------------------------------------------
/* output del comando java $JAVA/bin/Test */
Inizio test
Inizio metodo nativo
Fine metodo nativo
test()=0
Fine test
[marco@localhost test]$
----------------------------------------------------
/* output del comando java $JAVA/bin/Test & */
[7] 5670
[marco@localhost test]$ Inizio test
Inizio metodo nativo
# [segue, dopo la pressione del tasto ENTER]
[7]+  Stopped                 java Test
[marco@localhost test]$


Intendi se è possibile evitare di usare Java?
Sì, è possibile!
Ciao!

> Intendi se è possibile evitare di usare Java?
Spiritoso!
Ovviamente, vorrei:
1) eseguire l'applicazione java, comprensiva di metodo nativo che 
modifica le proprietà del terminale;
2) ritornare al prompt, in modo da poter dare, se necessario, nuovi 
comandi [se non dovessi disporre di un ambiente grafico].
3) evitare interruzioni non previste dell'applicazione.
N.B. L'applicazione non sembra terminare per colpa di java: infatti con 
il comando $JAVA/bin/java/Test & /* e non $JAVA/bin/Test &,scusate per 
il copia e incolla errato nel post iniziale */
viene stampata a schermo la scritta "Inizio metodo nativo", ma non "Fine 
metodo nativo". L'esecuzione della funzione C JNIEXPORT jint JNICALL 
Java_Test_test (JNIEnv * env,jobject obj) viene iniziata, ma non terminata.

 Spiritoso!
Veramente ero serio. Java fa schifo.

Non ho capito molto, ma in ogni caso il tuo utilizzo della funzione
"tcsetattr()" non ha senso. Cosa stai cercando di settare? Da dove
dovrebbero arrivare le impostazioni?
Visto che probabilmente sulla tua macchina hai anche un manuale delle
funzioni posix, ti consiglio un "man 3 tcsetattr" per vedere come
funziona la struct termios (o anche spiare nel file <termios.h>)

 funziona la struct termios (o anche spiare nel file <termios.h>)
Il mio problema, secondo me, non è quello, ma capire come funziona la 
"&" alla fine del comando:
"./comando &", avevo capito, significa 'esegui il programma "./comando" 
e torna al prompt senza aspettare la fine dell'esecuzione'.
Invece, sembra significare 'se possibile esegui il programma "./comando" 
e torna al prompt senza aspettare la fine dell'esecuzione', a meno 
che... ???' e in questo a meno che sembra rientrare l'uso di tcsetattr.

 "&" alla fine del comando:
Probabilmente il problema è dovuto al fatto che quando il processo va in 
background il suo flusso di input viene (giustamente) scollegato dal 
terminale. Dunque non ha molto senso andare a modificare gli attributi 
dell'input, o quantomento la richiesta dovrebbe bloccare l'esecuzione 
del processo finché non torna in foreground.
E.


Il fatto che non ci sia un utente al terminale non significa che non
arrivi input.
P.S. Ho fatto una prova, tanto per vedere se mi sfuggiva qualcosa. Sia
che il programma vada in background che no, i settaggi del terminale
relativo allo stdin sono impostati come richiesto. Più che altro
l'unica confusione che potrebbe nascere si ha quando il programma può
essere stoppato - in quel caso, essendo fermo, non fa proprio niente.
Magari controlla sulla tabella dei processi se non fosse questo il
caso.

 
Il processo non sembra bloccato, sembra proprio terminato prematuramente 
(riesco a smontare la partizione). E' possibile che vada in errore: 
esiste in C, qualcosa di simile al costrutto java
  try
   {/* codice /*}
  catch (Throwable t)
  {System.out.println(""+t);}
per controllare eventuali errori?
Ah, c'è modo, da C, di sapere se il processo è, o no, scollegato dal 
terminale?

 java nomeClasse
Ho isolato il problema nel seguente codice C (compilato con gcc test.c 
-o test). Ora il problema è: cosa fa uscire il programma, e come evitarlo?
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
static struct termios oldt;
void inizio()
  {tcgetattr(STDIN_FILENO,&oldt);}
void fine()
  {tcsetattr(STDIN_FILENO,TCSANOW,&oldt);}
int main(void)
  {
   printf("Inizio main\n");
   inizio();
   fine();
   printf("Fine main\n");
   return 0;
  }
----------------------------------------
#Output di ./test
Inizio main
Fine main
[marco@localhost test]$
----------------------------------------
#Output di ./test &
Inizio main
[3] 10153
[3]+  Stopped                 ./test
[marco@localhost test]$

 [marco@localhost test]$
Il codice C che hai postato fa quello che deve fare, ovvero stampa
"inizio main", prende le impostazioni del terminale, le reimposta, e
stampa "fine main". Se lo metti in background la shell lo stoppa,
visto che il programma deve aspettare di riottenere il terminale per
poterlo modificare. Tanto per essere ancora più chiari, se attacchi
qualcosa allo stdin tipo con una pipe (echo "ciao" | ./test &) il
programma continua a funzionare come dovrebbe.

 programma continua a funzionare come dovrebbe.
Ora mi è chiaro cosa succede: Stopped non significa (come pensavo) 
terminato, ma fermo in attesa di essere rimesso in esecuzione.
Infatti se digito ./test & l'output è:
###########################
Inizio main
[1] 11061
[1]+  Stopped                 ./test
[marco@localhost test]$
###########################
Se digito jobs ottengo l'elenco dei processi in attesa:
###########################
[1]+  Stopped                 ./test
[marco@localhost test]$
###########################
Se digito fg 1 faccio ripartire il processo 1 stoppato:
###########################
./test
Fine main
[marco@localhost test]$
###########################
Ora il problema diventa: posso sapere prima se è possibile eseguire 
"tcsetattr(STDIN_FILENO,TCSANOW,&oldt);" senza che venga stoppato il 
processo? Qualcosa tipo
static int siPuoEseguire=TRUE;
if (siPuoEseguireTcsetattrSenzaStoppare)
  {tcsetattr(STDIN_FILENO,TCSANOW,&oldt);}
else
  {siPuoEseguire=FALSE;}

   {siPuoEseguire=FALSE;}
Se non vuoi che la shell stoppi il processo (anche se non vedo perchè
mai, visto che il processo non può fare comunque niente su quel
terminale) devi sperare che la shell te lo dica. La bash ad esempio
spedisce il segnale SIGTTIN, che puoi catturare per "aggiustare" la
situazione.
Comunque ancora non riesco proprio a capire il perchè di tutto questo.

 situazione.
Ho risolto così:
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
static struct termios oldt;
void inizio()
  {tcgetattr(STDIN_FILENO,&oldt);}
void fine()
  {
   sigset_t block_ttou;
   sigemptyset(&block_ttou);
   sigaddset(&block_ttou, SIGTTOU);
   sigprocmask(SIG_BLOCK,&block_ttou,NULL);
   tcsetattr(STDIN_FILENO,TCSANOW,&oldt);
   sigprocmask (SIG_UNBLOCK,&block_ttou,NULL);
  }
int main(void)
  {
   printf("Inizio main\n");
   inizio();
   fine();
   printf("Fine main\n");
   return 0;
  }
 > Se non vuoi che la shell stoppi il processo (anche se non vedo perchè
 > mai, visto che il processo non può fare comunque niente su quel
 > terminale)
Questo, effettivamente, potrebbe essere un piccolo problema, ma al 
momento per me la cosa più importante era evitare che l'esecuzione 
dell'applicazione java venisse stoppata.
 > Comunque ancora non riesco proprio a capire il perché di tutto questo.
E' evidente che le righe che ho estratto e postato non hanno, da sole 
nessuna funzionalità, se non quella di indicare quale fosse il mio 
problema. Ripeto, il codice originale era evidentemente più lungo e 
complesso, e serviva a creare alcuni metodi nativi scritti in C per una 
classe java.
Grazie molte per l'aiuto, che, insieme a qualche googlata, è stato 
determinante.
Marco

 terminale) [CUT]
C'è modo di:
1) conoscere quale processo (A) è in foreground sul terminale corrente
2) mettere in foreground un processo (B), se non lo è già
3) rimettere in foregroung A [se A!=B]
?


Non so cosa stai cercando di ottenere, ma qualsiasi cosa tu stia
cercando di fare stai prendendo una strada sbagliata. Queste cose non
vengono praticamente mai gestite da un programma, visto che è compito
del sistema/ambiente operativo (che non lavora contro di te, anzi: se
fa qualcosa è perchè serve e ti toglie il pensiero di doverla
implementare).
Se invece stai facendo qualcosa che gestisce o fa un monitor dei
processi utilizza lo /proc, è lì anche per questo.

fma...@gmail.com
 ha scritto:

Semplicenente, la mia applicazione passa la maggior parte del tempo a 
fare il suo lavoro senza bisogno di interagire con chi l'ha lanciata, 
per cui preferirei poterla lanciare, se voglio, in background. Solo che, 
di tanto in tanto, deve comparire una richiesta per l'utente, a cui 
rispondere, semplicemente, s [=sì] o n [=no], per poi tornare allo stato 
iniziale. Tutto qui.
Vorrei ottenere, dall'interno di una funzione java, quello che si 
otterrebbe con l'uso combinato dei comandi jobs, fg e bg.

 otterrebbe con l'uso combinato dei comandi jobs, fg e bg.
Per questi casi infatti nessun programma che conosca si comporta in
questo modo. Di solito (al massimo) hanno due modalità, interattiva e
non. Se l'utente usa la modalità non interattiva il programma userà
sempre le azioni di default (magari specificabili da riga di comando),
mentre se vuole avere voce in capitolo tiene l'applicativo in
foreground (tipicamente in un'altro terminale virtuale) e quando ha
tempo/voglia risponderà alle sue richieste.
Come utente non oso pensare alla fine che può fare un programma che mi
interrompe mentre faccio qualcos'altro.

Marco ha scritto:

Ma se l'interfaccia utente deve essere java, non ti conviene far gestire 
a lei la richiesta del valore?
Ad es. la tua funzione potrebbe prendere in input un oggetto al quale 
richiedere i valori che ti servono di volta in volta, poi ci penserà il 
software java a chiedere il valore...
Ciao,
Tommaso

 software java a chiedere il valore...
In java, secondo me, il problema rimarrebbe al 100%, perché se in 
background non ho accesso all'input della tastiera con C non credo 
proprio che potrei averlo con java.
Inoltre, in java dovrei aspettare la pressione del tasto ENTER, mentre 
in C riesco a fare cose molto più fini.
Mi serve solo chiedere conferma all'utente se il programma deve 
terminare all'invio di un segnale, senza dover per questo riavviare il 
server (manutenzione, aggiornamento applicazione, ecc, quindi niente di 
prevedibile a priori). Ambiente testuale, e nessun utente a cui romperei 
le scatole,,,

 le scatole,,,
? Ovvero?

fma...@gmail.com
 ha scritto:

Ovvero, serve per un'applicazione che userei solo io, e che a me 
andrebbe bene cosě [e poi, per dirla tutta, sto "giocando" col C, dopo 
aver sempre scritto in java, anche per imparare qualcosa su questo 
linguaggio...]
La domanda rimane: dall'interno di un programma C posso sapere se il 
programma stesso sta girando in background o in foreground, oppure non č 
possibile? Poi sul cosa č piů opportuno fare nel primo caso [cercare di 
tornare al foreground per il tempo necessario, sollevare un'eccezione 
per impedire malfunzionamenti...) si puň discutere, ma il problema č: 
posso sapere se sto girando in background?

 le scatole,,,
Questo tipo di programmi, in C su Unix, normalmente hanno una azione di 
default che viene alterata tramite parametri sulla riga di comando.
Una cosa del tipo:
mytask --stoponhangup
oppure
mytask --nohangup
Nel codice di mytask c'è una sezione che verifica la riga di comando ed 
agisce di conseguenza.
Se la riga di comando è vuota, prendi una azione di default che 
stabilisci tu.
Se invece devi cambiare in corsa, le tecniche sono più sofisticate, si 
va dalle named pipes ai socket ai segnali utente alle shared memories ai 
semafori.
In java non saprei aiutarti, per ora, dato che sono agli inizi.
Andrea

 posso sapere se sto girando in background?
Sì, guardando la tabella dei processi.

 Sě, guardando la tabella dei processi.
Intendevo dall'interno del programma C

> Se invece devi cambiare in corsa
Esatto
 > le tecniche sono più sofisticate, si va dalle named pipes ai socket
 > ai segnali utente alle shared memories ai semafori.
Mi potete dare qualche link per capire di che si tratta?
 > In java non saprei aiutarti, per ora, dato che sono agli inizi.
No, dovrei farlo dall'interno del programma C, poi e trarnr conseguenze 
per il programma java (eccezione?) si vedrà.
Grazie

 Intendevo dall'interno del programma C
Se cerchi una funzione "isInBackground()" non esiste niente di simile.
Ancora una volta, leggi lo /proc. Non capisco perchè non accetti
l'idea di leggere una cartella o dei file. Un programma unix legge e
scrive su file QUALSIASI cosa debba fare.
Se stai facendo questa cosa per imparare qualcosa di unix magari
troveresti più giovamento buttando il java e il tuo codice C,
scaricare i sorgenti delle coreutils e leggere come si scrive in
realtà un programma vero (come "cat" o "less" - vai tranquillo, son
corti corti).

 ai segnali utente alle shared memories ai semafori.
Ho dato uno sguardo molto rapido, ma se non ho capito male si tratta di 
tecniche per far comunicare tra di loro dei processi. Io non devo far 
comunicare tra loro dei processi, ma solo (o perlomeno prima) sapere se 
il processo corrente ha il controllo della tastiera. Non so se esistono 
altri processi con cui comunicare; se esistono non sono parti di miei 
programmi (e quindi non c'è alcun motivo per supporre che debbano 
rispondermi). La situazione è questa:
A) Se ho il controllo del terminale, richiamo una funzione int getCh() e 
restituisco il codice del tasto premuto.
B) Se non ho il controllo del terminale, allora ho le seguenti possibilità:
1) anziché richiamare getCh, setto in qualche modo una variabile (oppure 
ritorno un particolare valore, per es. <0) in modo che l'applicazione 
java possa lanciare un'eccezione;
2) oppure, impongo il controllo del terminale all'applicazione corrente 
per il tempo necessario, per poi ripristinare la situazione precedente;
3) oppure, attuo la soluzione 1) o 2) a seconda di un parametro 
(boolean) passato al costruttore della classe java che contiene il 
metodo nativo.
Come si vede, precondizione per qualunque soluzione è che il programma C 
deve essere in grado di capire se ha, o no, il controllo del terminale.

 troveresti più giovamento buttando il java e il tuo codice C [CUT]
Un po' sì e un po' no... volevo scrivere una libreria con metodi nativi 
per java, ma mi sarebbe piaciuto renderla funzionale anche se non avessi 
avuto il controllo della tastiera (già la possibilità di far lanciare 
un'eccezione al programma java se non si ha il controllo del terminale, 
piuttosto che far impallare l'applicazione ad aspettare come un fesso 
qualcosa che non arriverà mai, sarebbe qualcosa).
N.B. Comunque ho provato q scrivere un piccolo programma java di prova 
che usa BufferedReader, InputStreamReader e System.in e ho visto che 
effettivamente se non ho il controllo del terminale viene stoppato il 
processo esattamente come succedeva al mio programma C...

Marco ha scritto:

Chiedo scusa, mi sono perso... Non riesco a capire perché ti devi 
complicare la vita.
Perché devi interagire con il terminale (non rispondermi 'perché devo 
leggere un tasto', altrimenti ti plonko)?
E' necessaria questa interazione?
Ci sono altri modi per fare quello che vuoi?
Andrea


Guarda che non è casuale - se lo metti in background in controllo del
terminale in questo caso non ce l'avrai MAI.
Se ti piacerebbe scegliere lato utente se metterlo o meno in
background, fai un programma che va SEMPRE in background (con una fork
()) almeno che non lo specifichi (niente fork(), lo lasci come
l'utente lo lancia). Se l'utente è chiede la modalità interattiva e lo
mette in background manualmente con & significa che VUOLE che il
programma si comporti in quel modo.

 iniziale. Tutto qui.
Un programma lasciato in background si comporta esattamente nel modo da 
te richiesto. Finché non ha bisogno dell'input dell'utente fa le cose 
per i fatti suoi, appena ha bisogno dell'input si STOPpa in attesa di 
tornare in foreground e di leggere l'input. Il problema, se capisco 
bene, è che il tuo programma ha modificato i settaggi dell'input per 
ricevere i caratteri senza bufferizzazione. A quel punto il programma 
potrebbe ricevere l'input in qualunque momento, e quindi si blocca 
immediatamente. Dovresti modificare i settaggi dell'input solo 
immediatamente prima di chiedere l'input. In questo modo finchè non ha 
bisogno dell'utente il programma dovrebbe girare tranquillamente in 
background. Dopo che hai ricevuto l'input rimetti a posto tutto...
E.

 background. Dopo che hai ricevuto l'input rimetti a posto tutto...
E' esattamente quello che volevo fare, solo non ho trovato altro modo 
che quello descritto sopra...


Esasperante. Tu non hai un problema con il C, ce l'hai con unix. Non
prenderla come sarcasmo, è una constatazione. Fossi in te posterei lo
stesso messaggio su comp.unix.programmer, tanto per vedere cosa ti
rispondono.
(A parte tutto lo farei davvero, di certo è un gruppo più esperto di
questo per questo tipo di problemi).

 allo stato iniziale. Tutto qui.
Se il programma è in background come fa a sapere se l'utente vuole 
rispondere sì o no? Deve NECESSARIAMENTE fermarsi e aspettare di essere 
riportato in foreground.
E.

>> [=no], per poi tornare allo stato iniziale. Tutto qui.
E perche non usi due thread? uno rimane sospeso attendendo input
e l'altro fa quello che deve. Con tutti i meccanismi di sincronizzazione
offerti , c'e' solo l' imbarazzo della scelta...
B.

 offerti , c'e' solo l' imbarazzo della scelta...
Perché i thread che devono ricevere l'input sono due: bash [ovvero il 
terminale deve rimanere disponibile per ricevere eventuali comandi 
dall'utente), e l'applicazione, in rari casi. Prendo atto che non si può 
fare (perlomeno, non da programma come desideravo io), e pazienza.
N.B. Non è possibile conoscere il pid dell'applicazione corrente 
[dall'interno dell'applicazione, intendo], vero?


getpid()

 [dall'interno dell'applicazione, intendo], vero?
getpid()
B.

 dalla shell e il tuo pgm.
Ho cercato di riformulare il mio problema:
1) Un'applicazione a, con pid x, lanciata con ./a &, gira su un 
terminale in background
2) un'applicazione b, con pif y, lanciata con ./b, gira sullo stesso 
terminale in foreground.
Può l'applicazione b cedere il controllo del terminale all'applicazione 
con pid x?

 con pid x?
Indipendentemente se si possa o no fare, è un design sbagliato. B non
deve "cedere il controllo del terminale", bensì A e B devono
"comunicare in caso di bisogno". Un oggetto (processo) di vista e un
oggetto (processo) di modello (seguendo un classico pattern
architetturale MVC). E' quello che cerchiamo di dirti tutti da 40 post
circa.

 "comunicare in caso di bisogno".
Ho trovato ESATTAMENTE la risposta a quello che cercavo: lanciare 
l'applicazione da un terminale virtuale anziché in background.
N.B. Mi sembra comunque interessante il discorso delle shared memory 
(per tutt'altre questioni, però), anche se non ho ancora capito 
abbastanza bene la cosa da implementare codice. Cercherò di approfondire.
http://www.lovebash.org/2008/05/19/controllo-remoto-di-applicazioni-nella-shell/
Quando lanciamo nel nostro terminale un programma che puo’ durare 
diverse ore, puo’ essere comodo potersi scollegare dal terminale stesso, 
oppure dall’intero sistema, in particolare se il pc che stiamo 
utilizzando non e’ il nostro personale. Oppure nel caso in cui stiamo 
lanciando un’applicazione su un pc remoto via collegamento ssh.
In questi casi ci viene in aiuto l’utility screen. Con questo piccolo 
programma e’ possibile aprire un terminale virtuale da cui scollegarci 
senza fermare le applicazioni lanciate per poi ricollegarci allo stesso 
terminale in un altro momento o da un altro pc.
Innanzi tutto lanciamo screen
screen
Dopo aver letto le eventuali note iniziali ed aver premuto invio, ci 
troveremo davanti l’ambiente screen, che ricalca in tutto e per tutto un 
normale terminale. Possiamo quindi lanciare la nostra applicazione nel 
modo consueto.
Per scollegarci dal terminale virtuale non dobbiamo far altro che 
digitare in sequenza i tasti Ctrl+a seguito da d. Saremo cosi’ tornati 
al nostro consueto terminale.
Per ripristinare invece una sessione screen precedentemente abbandonata, 
basta usare il comando
screen -r
Se avevamo messo in sospeso piu’ d una sessione screen, il programma ci 
chiedera’ quale ripristinare, proponendoci una lista. Bastera’ ripetere 
quindi lo stesso comando indicando anche l’ID della sessione da 
ripristinare.

 lanciando un’applicazione su un pc remoto via collegamento ssh.
In questo caso puoi lanciare il programma tramite il comando "nohup". 
Questo fa sì che se il programma è in background e il terminale si 
chiude, il programma non viene "impiccato", ma continua tranquillamente 
ad essere eseguito. Certo con screen puoi ricollegarti al terminale, 
cosa che forse non è possibile fare con nohup.
E.