View Full Version : [Schell]
Kleidemos
04-09-2003, 12:51
Una shell puo essere una cosa tipo questo???
*( schell ) ( char[]* );
int main( int argc, char *argv[] )
{
if ( argv[0] == cp )
{
schell = cp;
schell(argv[1], argv[2]);
}else if( argv[0] == mv)
{
schell = mv;
schell(argv[1], argv[2]);
}else if( argv[0] == rm)
{
schell = rm;
schell(argv[1], argv[2]);
}
}
Guarda che argv[0] è il nome del file eseguibile che contiene il main...
Comunque non capsico cosa tu intenda...
nemmeno io ho capito
ma perche' la chiami schell? c'e' una c di troppo
ilsensine
04-09-2003, 13:20
Originariamente inviato da Kleidemos
Una shell puo essere una cosa tipo questo???
Qualsiasi cosa dovrebbe fare il programma che hai scritto, la risposta è NO.
Cosa vuoi fare? Un emulatore di bash ad esempio?
Kleidemos
04-09-2003, 13:22
si, un emulatore di bash
ilsensine
04-09-2003, 13:52
Allora dovresti rivedere il problema. Devi fare un interprete di comandi, ovvero un programma che fa queste cose:
- leggere l'input da tastiera
- divide l'input in nome "comando parametro1 parametro2 ..."
- se il comando è un comando conosciuto dall'interprete, allora chiama la funzione che lo implementa; altrimenti assume che il comando è un eseguibile, e lo lancia (vedi fork+execve ecc.) -- ad es. "cd" viene eseguito da bash (non è un programma esterno), mentre "more" è un programma esterno.
Io ho scritto una shell per un progetto all'università... Non è facile. Ti devi implementare tutto il meccanismo di job control e di history. Inoltre devi avere un buon meccanismo di signal handling. Una shell non deve mai crashare, se il programma che ha avviato crasha, ma deve gestire la situazione.
Mi ha dato grosse soddisfazioni questo progetto. Alla fine ho avviato KDE dalla mia shell e il comando top mostrava esattamente la gerarchia dei processi a partire dal ppid (parent pid) della mia shell ;) ;) Una cosa di cui ne vado fierissimo. Peccato che non ho tempo di continuarla. Vorrei per esempio aggiungere un piccolo interprete interno per l'interpretazione di script ...
A proposito kleidemos, per riagganciarmi al discorso che facevamo sull'importanza delle strutture di dati ... Per il job controlling ho usato una lista di liste, dove il nodo della prima lista era il job, che conteneva un link ad un'altra lista che erano i processi. (Come vedi devi anche creare un meccanismo per formalizzare il concetto di job ;) che non è lo stesso di processo ;)
Per l'history ho usato una lista circolare doppiamente concatenata ;)
Come vedi le strutture dati sono il cuore di ogni programma ben disegnato.
Kleidemos
04-09-2003, 18:47
Originariamente inviato da mjordan
A proposito kleidemos, per riagganciarmi al discorso che facevamo sull'importanza delle strutture di dati ... Per il job controlling ho usato una lista di liste, dove il nodo della prima lista era il job, che conteneva un link ad un'altra lista che erano i processi. (Come vedi devi anche creare un meccanismo per formalizzare il concetto di job ;) che non è lo stesso di processo ;)
Per l'history ho usato una lista circolare doppiamente concatenata ;)
Come vedi le strutture dati sono il cuore di ogni programma ben disegnato.
:eek:
Lo hai ancora il cod?
Originariamente inviato da Kleidemos
:eek:
Lo hai ancora il cod?
Certo che lo ho ... Secondo te lo butto?? ;)
Originariamente inviato da mjordan
Certo che lo ho ... Secondo te lo butto?? ;)
Ok...Non chiedermi di darti tutto il pacchetto però .... Non mi va di pubblicarlo...Nei sorgenti ci sono molte informazioni personali che servivano per verbalizzare l'esame (tipo matricola, nome, cognome, università, anno di corso e via dicendo ) e poichè sono 12 file di codice sorgente per circa 2400 righe di codice (esclusi Makefiles) non mi va di mettermi a togliere tutta sta roba ...
Ma se vuoi ti posto il job controller (in fondo è quello il cuore di una shell) ;)
Se riesco ti posto pure alcune figure che misi nella relazione finale ... Ovvero le strutture dati usate e lo shot con il ppid in azione :D
Anzi, non ti posto niente perchè nell'altro thread mi hai dato dello stronzo :bimbo: :fuck:
Kleidemos
05-09-2003, 07:06
Originariamente inviato da mjordan
Anzi, non ti posto niente perchè nell'altro thread mi hai dato dello stronzo :bimbo: :fuck:
io?
Originariamente inviato da Kleidemos
io?
Dormi poco tu ah? :D
Kleidemos
05-09-2003, 07:17
Originariamente inviato da mjordan
Dormi poco tu ah? :D
:cry:
Originariamente inviato da Kleidemos
:cry:
Non piangere :kiss:
Se ti può consolare ho appena finito di studiare ... :zzz: :coffee:
Kleidemos
05-09-2003, 07:25
cazzo:eek:
Originariamente inviato da Kleidemos
cazzo:eek:
"Notte" kleidemos ;)
:coffee: :coffee: :coffee: :coffee: :coffee:
Kleidemos
05-09-2003, 07:30
notte :ave: :ave:
Allora questa è la struttura dati dell'history (lista doppiamente concatenata con ultimo nodo circolare):
Questa è la struttura dati del job controller (lista singolarmente concatenata di liste singolarmente concatenate. La principale rappresenta i job da eseguire, le sottoliste sono i processi del job.
Notare: Tutte le immagini create con "The Gimp" :cool:
maxithron
05-09-2003, 08:53
tempo fa per una cosa del genere mi ritornò utile questo tipo di esempio:
http://linux.disco.unimib.it/~levan/sistemi/SI03CORSOLAB/LUCI/ux2-0510.pdf
Questo è l'header principale del job controller:
/* -*-linux-c-*- */
/***************************************
* jobs.h - Header file per jobs.c
* xxxxxx xxxxxx - Matricola xxxxxx
* Lab. di Sistemi Operativi.
* Progetto "SHELL"
* Universita' degli Studi di xxxxxxxx.
***************************************/
#ifndef __JOBS_H__
#define __JOBS_H__
#include <termios.h>
/* Tipi di dato che definiscono processo e job */
/* Un processo. */
typedef struct process {
struct process * next; /* Il prossimo processo nella pipeline di processi. */
char ** argv; /* Per exec */
pid_t pid; /* Process ID (PID) */
char completed; /* TRUE se il processo e' terminato. */
char stopped; /* TRUE se il processo e' stoppato. */
int status; /* Valore di status riportato da un processo. */
} process;
/* Un job. */
typedef struct job {
struct job * next; /* Il prossimo job attivo. */
char * command; /* Linea di comando, usata per il messaggi. */
process * first_process; /* La lista di processi attivi in questo job. */
pid_t pgid; /* Process Group ID (PGID) */
char notified; /* TRUE se l'utente e' stato avvertito di job stoppati. */
struct termios tmodes; /* Per il salvataggio delle informazioni sul terminale. */
int stdin, stdout, stderr; /* Canali I/O standard. */
int state; /* Possibile stato di un job: FG (1), BG (0) */
} job;
/* Variabili globali che tengono traccia degli attributi della shell. */
pid_t shell_pgid;
struct termios shell_tmodes;
int shell_terminal;
int shell_is_interactive;
/* Prototipi di funzione. */
void init_shell(void);
int mark_process_status(pid_t pid, int status);
void mark_job_as_running(job * j);
job * find_job(pid_t pgid);
int job_is_stopped(job * j);
int job_is_completed(job * j);
void format_job_info(job * j, const char * status);
void wait_for_job(job * j);
void put_job_in_foreground(job * j, int cont);
void put_job_in_background(job * j, int cont);
void continue_job(job * j, int foreground);
void launch_process(process * p, pid_t pgid,
int infile, int outfile, int errfile,
int foreground);
void launch_job(job * j);
void update_status(void);
void free_job(job * j);
void free_all_job(void);
void do_job_notification(void);
process * build_process(char ** argv);
job * build_job(char * command, int filein, int fileout,
int filerr, int state);
void attach_process_to_job(job * j, process * p);
void launch_all_job(void);
#endif /* !__JOBS_H__ */
E finalmente il job controller:
/* -*-linux-c-*- */
/***************************************************
* jobs.c - Funzioni per la manipolazione dei jobs
* xxxxxx xxxxxx - Matricola xxxxxx
* Lab. di Sistemi Operativi.
* Progetto "SHELL"
* Universita' degli Studi di xxxxxxx.
***************************************************/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#include <ncurses.h>
#include "jobs.h"
#include "signalh.h"
#include "error.h"
/* I job attivi sono memorizzati in una lista concatenata. Questa e' la testa. */
job * first_job = NULL;
/* Variabili esterne definite altrove. */
extern int verbose;
extern int x, y;
/*********
* Inizializza la shell. Si accerta che la shell stia girando
* interattivamente come un processo in foreground prima di procedere.
* Questo e' fondamentale nel caso la shell sia avviata da un'altra shell.
*********/
void
init_shell(void)
{
/* Vede se stiamo girando in modo interattivo. */
shell_terminal = STDIN_FILENO;
shell_is_interactive = isatty(shell_terminal);
if (shell_is_interactive) {
/* Cicla fino a quando non siamo in foreground. */
while (tcgetpgrp(shell_terminal) != (shell_pgid = getpgrp()))
kill(- shell_pgid, SIGTTIN);
/* Ignoriamo i segnali interattivi e quelli di job control. */
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
#ifdef __DEBUG
/* Per stampare un backtrace prima di un eventuale crash. */
signal(SIGSEGV, sigsegv_handler);
#endif
/* Mettiamo noi stessi nel nostro process group. */
shell_pgid = getpid();
if (setpgid(shell_pgid, shell_pgid) < 0) {
perror("Impossibile mettere la shell nel suo stesso process group.");
exit(1);
}
/* Prende possesso del terminale. */
tcsetpgrp(shell_terminal, shell_pgid);
/* Salva gli attributi di default del terminale. */
tcgetattr(shell_terminal, &shell_tmodes);
}
/* Pare tutto ok? :-) */
return;
} /* init_shell() */
/*********
* Memorizza lo status del processo associato al pid `pid',
* ritornato dalla funzione waitpid().
* Ritorna 0 se tutto e' andato per il verso giusto, -1 in
* caso di errore.
*********/
int
mark_process_status(pid_t pid, int status)
{
if (pid > 0) {
/* Aggiorniamo il campo per il processo. */
for (job * j = first_job; j; j = j->next)
for (process * p = j->first_process; p; p = p->next)
if (p->pid == pid) {
p->status = status;
if (WIFSTOPPED(status))
p->stopped = 1;
else {
p->completed = 1;
if (WIFSIGNALED(status))
fprintf(stderr, "%d: Terminato dal segnale %d: %s\n",
(int)pid, WTERMSIG(p->status), strerror(errno));
}
return 0;
}
/* Il processo con il PID=pid non e' presente nella lista. */
fprintf(stderr, "Nessun processo figlio %d.\n", pid);
return -1;
}
else
if (pid == 0 || errno == ECHILD)
/* Il processo non e' ancora pronto. */
return -1;
else {
unix_error("waitpid");
return -1;
}
} /* mark_process_status() */
/*********
* Setta un job stoppato `j' nello stato di "esecuzione".
*********/
void
mark_job_as_running(job * j)
{
for (process * p = j->first_process; p; p = p->next)
p->stopped = 0; /* `Running' significa `non stopped' */
j->notified = 0;
return;
} /* mark_job_as_running() */
/*********
* Trova il job attivo con lo specificato pgid.
*********/
job *
find_job(pid_t pgid)
{
for (job * j = first_job; j; j = j->next)
if (j->pgid == pgid)
return j;
return NULL;
} /* find_job() */
/*********
* Ritorna TRUE se tutti i processi nel job sono
* stati stoppati o hanno completato la loro esecuzione.
*********/
int
job_is_stopped(job * j)
{
for (process * p = j->first_process; p; p = p->next)
if (!p->completed && !p->stopped)
return 0;
return 1;
} /* job_is_stopped() */
/*********
* Ritorna TRUE se tutti i processi nel job hanno
* terminato la loro esecuzione.
*********/
int
job_is_completed(job * j)
{
for (process * p = j->first_process; p; p = p->next)
if (!p->completed)
return 0;
return 1;
} /* job_is_completed() */
/*********
* Fornisce informazioni sullo stato dei job per l'utente.
*********/
void
format_job_info(job * j, const char * status)
{
fprintf(stderr, "%ld (%s): %s\n", (long int)j->pgid, status, j->command);
return;
} /* format_job_info() */
/*********
* Controlla i processi che hanno le informazioni di stato disponibili
* bloccandoli fino a quanto tutti i processi nel job hanno terminato
* la loro esecuzione.
*********/
void
wait_for_job(job * j)
{
int status;
pid_t pid;
do {
pid = waitpid(WAIT_ANY, &status, WUNTRACED);
} while (!mark_process_status(pid, status)
&& !job_is_stopped(j)
&& !job_is_completed(j));
return;
} /* wait_for_job() */
/*********
* Imposta il job j in foreground. Se `cont' e' diverso da zero
* ripristina le impostazioni del terminale precedentemente salvate
* e invia al process group un SIGCONT per aspettarlo prima di bloccarlo.
*********/
void
put_job_in_foreground(job * j, int cont)
{
/* Ok. Mettiamo il job in foreground. */
j->state = 1;
tcsetpgrp(shell_terminal, j->pgid);
/* Se necessario inviamo al job un segnale di continuazione. */
if (cont) {
tcsetattr(shell_terminal, TCSADRAIN, &j->tmodes);
if (kill(- j->pgid, SIGCONT) < 0)
unix_error("kill (SIGCONT)");
}
/* Attendiamo il job che ritorni. */
wait_for_job(j);
/* Ripristiniamo la shell in foreground. */
tcsetpgrp(shell_terminal, shell_pgid);
/* Ripristiniamo lo stato del terminale della shell. */
tcgetattr(shell_terminal, &j->tmodes);
tcsetattr(shell_terminal, TCSADRAIN, &shell_tmodes);
/* Fatto tutto? ;-) */
return;
} /* put_job_in_foreground() */
/*********
* Imposta il job j in background. Se `cont' e' diverso da zero
* invia al process group un segnale SIGCONT per aspettarlo.
*********/
void
put_job_in_background(job * j, int cont)
{
j->state = 0;
/* Invia al job un segnale di continuazione se necessario. */
if (cont)
if (kill(- j->pgid, SIGCONT) < 0)
unix_error("kill (SIGCONT)");
return;
} /* put_job_in_background() */
/*********
* Continua l'esecuzione di un job `j'.
*********/
void
continue_job(job * j, int foreground)
{
mark_job_as_running(j);
if (foreground)
put_job_in_foreground(j, 1);
else
put_job_in_background(j, 1);
return;
} /* continue_job() */
void
launch_process(process * p, pid_t pgid,
int infile, int outfile, int errfile,
int foreground)
{
pid_t pid;
/***
* Cicla all'infinito in modo da "non avviare" l'esecuzione del child process
* fin quando non si setta a 0 la variabile `i' tramite il debugger.
***/
#if __DEBUG /* Consente al debugger di seguire un processo dopo una fork() */
volatile int i = 1;
while(i);
#endif
if (shell_is_interactive) {
/* Mettiamo il processo nel proprio process group
* e diamo al process group il controllo del terminale.
* Questo dev'essere fatto sia dalla shell che dai processi,
* per evitare il verificarsi di potenziali race conditions. */
pid = getpid();
if (pgid == 0)
pgid = pid;
setpgid(pid, pgid);
if (foreground)
tcsetpgrp(shell_terminal, pgid);
/* Ripristiniamo gli handler dei segnali per il processo in corso. */
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGTTIN, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
}
/* Setta i canali standard di I/O per il nuovo processo. */
if (infile != STDIN_FILENO) {
dup2(infile, STDIN_FILENO);
TEMP_FAILURE_RETRY(close(infile));
}
if (outfile != STDOUT_FILENO) {
dup2(outfile, STDOUT_FILENO);
TEMP_FAILURE_RETRY(close(outfile));
}
if (errfile != STDERR_FILENO) {
dup2(errfile, STDERR_FILENO);
TEMP_FAILURE_RETRY(close(errfile));
}
/* Esegue il nuovo processo. Ci si accerta che usciamo. */
execvp(p->argv[0], p->argv);
app_error(p->argv[0]);
_exit(EXIT_FAILURE);
} /* launch_process() */
void
launch_job(job * j)
{
pid_t pid;
int mypipe[2]; /* pipe per redirezione (`>' e pipe `|') */
int infile, outfile;
infile = j->stdin;
for (process * p = j->first_process; p; p = p->next) {
/* Setta una pipe, se necessario. */
if (p->next) {
if (pipe(mypipe) < 0) {
unix_error("pipe");
exit(1);
}
outfile = mypipe[1];
}
else /* Catturiamo l'output */
outfile = j->stdout;
/* Forka il processo figlio. */
pid = fork();
if (pid == 0) /* Questo e' il processo figlio. */
launch_process(p, j->pgid, infile, outfile,
j->stderr, j->state);
else
if (pid < 0) { /* La fork() ha fallito. */
unix_error("fork()");
exit(1);
}
else { /* Questo e' il processo genitore. */
p->pid = pid;
if (shell_is_interactive) {
if (!j->pgid)
j->pgid = pid;
setpgid(pid, j->pgid);
}
}
/* Da' una pulitina dopo le pipe. :-) */
if (infile != j->stdin)
close(infile);
if (outfile != j->stdout)
close(outfile);
infile = mypipe[0];
} /* for() */
if (verbose)
format_job_info(j , "eseguito");
if (!shell_is_interactive)
wait_for_job(j);
else {
if (j->state)
put_job_in_foreground(j, 0);
else
put_job_in_background(j, 0);
}
} /* launch_job() */
/*********
* Controlla i processi che hanno le informazioni di status
* disponibili senza bloccarli.
*********/
void
update_status(void)
{
int status;
pid_t pid;
do {
pid = waitpid(WAIT_ANY, &status, WUNTRACED | WNOHANG);
} while (!mark_process_status(pid, status));
return;
} /* update_status() */
/*********
* Scorre la pipeline dei processi del job `j'
* deallocando la memoria delle strutture.
*********/
void
free_job(job * j)
{
process * p;
assert(j != NULL);
while (j->first_process) {
p = j->first_process;
j->first_process = j->first_process->next;
if (verbose)
printw("Libero il processo %s del job %s\n", *p->argv, j->command);
free(p);
}
j->first_process = NULL;
free(j);
return;
} /* free_job() */
/*********
* Scorre l'intera pipeline dei jobs,
* chiamando per ogni job incontrato free_job()
*********/
void
free_all_job(void)
{
job * j = first_job;
assert(first_job != NULL);
/* Scorriamo l'intera pipeline dei jobs. */
while (first_job) {
first_job = first_job->next;
free_job(j);
j = first_job;
}
assert(first_job == NULL);
return;
} /* free_all_job() */
/*********
* Notifica all'utente i job stoppati o terminati.
* Inoltre cancella i job terminati dalla lista dei job attivi.
*********/
void
do_job_notification(void)
{
job * jlast;
job * jnext;
/* Aggiorniamo le informazioni di stato per il processo figlio. */
update_status();
jlast = NULL;
for (job * j = first_job; j; j = jnext) {
jnext = j->next;
/* Se tutti i processi hanno completato la loro esecuzione,
* dice all'utente che il job ha terminato e lo cancella
* dalla lista dei job attivi.
*/
if (job_is_completed(j)) {
format_job_info(j, "completato");
if (jlast)
jlast->next = jnext;
else
first_job = jnext;
free_job(j);
}
else
/* Notifica l'utente della presenza di job stoppati,
* marcando questo evento, cosi' che sia impossibile
* stoppare un job per piu' di una volta.
*/
if (job_is_stopped(j) && !j->notified) {
format_job_info(j, "stoppato");
j->notified = 1;
jlast = j;
}
else
/* Inutile dire qualcosa se il processo
* sta ancora girando. ;-)
*/
jlast = j;
} /* for() */
return;
} /* do_job_notification() */
/*********
* Alloca la memoria per un processo e compila
* i campi base per la sua esecuzione.
*********/
process *
build_process(char ** argv)
{
process * p;
p = (process *)malloc(sizeof(struct process));
p->next = NULL;
p->argv = argv;
return p;
} /* build_process() */
/*********
* Alloca la memoria per un job. Scorre la lista dei job
* gia' presente e determina la corretta posizione per
* inserire il nuovo job. Compila i campi della struttura in base
* ai seguenti parametri:
* command: Il nome del job.
* filein, fileout, filerr: I canali standard che il job deve usare.
* state: foreground (1) o background (0)?
*********/
job *
build_job(char * command, int filein, int fileout, int filerr, int state)
{
job * j = NULL;
if (first_job) { /* C'e' gia' un job in esecuzione. */
for (job * aux = first_job; aux; aux = aux->next) { /* Scorre la lista dei job */
if (aux->next == NULL) { /* Siamo nell' ultimo job. */
j = (job *)malloc(sizeof(struct job));
j->next = NULL;
aux->next = j;
/* Compilazione campi job. */
j->command = command;
j->first_process = NULL;
j->stdin = filein;
j->stdout = fileout;
j->stderr = filerr;
j->state = state;
}
}
}
else { /* Il primo job in esecuzione. */
j = (job *)malloc(sizeof(struct job));
j->next = NULL;
first_job = j;
/* Compilazione dei campi job. */
j->command = command;
j->first_process = NULL;
j->stdin = filein;
j->stdout = fileout;
j->stderr = filerr;
j->state = state;
}
return j;
} /* build_job() */
/*********
* Aggiunge il processo p in coda alla pipeline dei processi
* del job j.
*********/
void
attach_process_to_job(job * j, process * p)
{
if (!j->first_process)
j->first_process = p;
else /* Scorriamo la pipeline dei processi del job. */
for (process * aux = j->first_process; aux; aux = aux->next)
if (aux->next == NULL) { /* Siamo nell'ultimo processo. */
aux->next = p;
return;
}
return;
} /* attach_process_to_job() */
/*********
* Scorre la pipeline dei job, eseguendoli uno per uno.
* Questo si verifica nel caso viene utilizzato l'operatore
* `;' nella riga di comando.
*********/
void
launch_all_job(void)
{
assert(first_job != NULL);
/* Scorre la lista dei job e li esegue tutti. */
for (job * j = first_job; j; j = j->next)
launch_job(j);
return;
} /* launch_all_job() */
Tutto questo per rispondere a chi dice che IL MODELLO E' UNA GROTTA :D :D :D :D
Originariamente inviato da maxithron
tempo fa per una cosa del genere mi ritornò utile questo tipo di esempio:
http://linux.disco.unimib.it/~levan/sistemi/SI03CORSOLAB/LUCI/ux2-0510.pdf
Si ma ricorda. Il modello è una GROTTA!!! :sofico: :D :p
Kleidemos, sei soddisfatto?? :p
maxithron
05-09-2003, 09:14
Originariamente inviato da mjordan
Si ma ricorda. Il modello è una GROTTA!!! :sofico: :D :p
:D ed in questo caso chi sarebbero il bue e l'asinello???:p
Originariamente inviato da maxithron
:D ed in questo caso chi sarebbero il bue e l'asinello???:p
Mo non ti sto capendo :D
maxithron
05-09-2003, 09:37
intendevo fare una metafora un pò azzardata forse tra la GROTTA(e non so perchè mi è venuta in mente la grotta del Gesù bambino...)e quindi di conseguenza, il bue e l'asinello:confused: :eek: :eek:
P.S:non mi ero accorto di essere ridotto così male....vado a prendere un saldatore per ricongiungere le poche sinapsi che mi son rimaste.....
Kleidemos
05-09-2003, 09:39
Originariamente inviato da mjordan
Kleidemos, sei soddisfatto?? :p
Certo, Ttnk 10000000000000000000000000000000
E che sto anche traducendo delle versioni greche (sai i ripassi pre-scuola e i :D compiti:D ) e nn ho letto subito le mail:muro:
Originariamente inviato da maxithron
intendevo fare una metafora un pò azzardata forse tra la GROTTA(e non so perchè mi è venuta in mente la grotta del Gesù bambino...)e quindi di conseguenza, il bue e l'asinello:confused: :eek: :eek:
P.S:non mi ero accorto di essere ridotto così male....vado a prendere un saldatore per ricongiungere le poche sinapsi che mi son rimaste.....
:D :D Succede succede anche a me :D :D
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.