Compare commits

...

10 Commits

Author SHA1 Message Date
032faead63 cleaned up whitespace in the latex files, deleted guest password 2023-03-27 23:04:18 +02:00
6fef3a3c7c removed old guest password 2023-03-27 22:57:18 +02:00
11879cf252 fixed spacing 2022-06-08 12:33:03 +02:00
f228ee5462 Opdate README 2022-05-20 19:53:43 +02:00
8511272a65 removed .gitkeep from scripts folder 2022-05-20 19:41:30 +02:00
7a1ad2a61c escluso pdf in gitignore 2022-05-20 19:36:07 +02:00
db82cf8a76 finita relazione 2022-05-20 19:34:54 +02:00
4283d5450e added LRU and fixed bug in dequeueN 2022-05-19 21:32:44 +02:00
27be55993c minor fix 2022-05-18 02:24:43 +02:00
a1e7c2e995 function 'request' for handling readN serverside 2022-05-17 18:41:50 +02:00
13 changed files with 567 additions and 180 deletions

3
.gitignore vendored
View File

@ -169,4 +169,5 @@ flycheck_*.el
*.aux
*.log
*.synctex.gz
*.toc
*.toc
*.pdf

View File

@ -154,7 +154,6 @@ test3: all
@echo "backlog = 100" >> $(BUILD_DIR)/config.ini
$(BUILD_DIR)/server $(BUILD_DIR)/config.ini &
bash scripts/test3.sh &
@echo ""
@echo "waiting for 30 seconds"
@sleep 30
pkill --signal SIGINT -f $(BUILD_DIR)/server

178
README.md
View File

@ -1,3 +1,177 @@
# Progetto
# Introduzione
Progetto del Laboratorio di Sistemi Operativi
Il progetto realizzato consiste in un file storage gestito da un server
multithreaded e un client che possono comunicarsi attraverso le funzioni
implementate dall'API. Il codice è presente sulla repository privata al
link: [tautocrono.it](http://tautocrono.it).\
Si può accedere usando le credenziali:
::: center
Username Password
---------- -----------------
Guest
:::
Il progetto è compilato in modo automatico tramite makefile con i
target: all, test1, test2, test3. Il target all fa partire un'altro
sottoprocesso make che eseguirà il target multi con degli argomenti
aggiuntivi, tipo `-j` in modo da rendere la compilazione multiprocesso.
Ci sono anche i target clean e cleanall che rimuovono gli eseguibili
creati con i precedenti target.
Sono inclusi anche dei file per testare le funzionalità del client e del
server nella cartella *testFiles*.
Gli oggetti vengono creati nella directory *obj/*, mentre gli eseguibili
vengono creati nella directory *build/*. La directory *src/* contiene il
codice che fa parte del main del server e del client; il resto del
codice è contenuto nella directory *lib/*.
È stata implementata sia la politica di rimpiazzamento FIFO che la
politica LRU.
# Server
Il server prende come unico argomento il path al file di configurazione.
Un esempio di file di configurazione è `config.ini` presente nella root
del progetto. La struttura del file è analoga a quella di un file
[INI](https://en.wikipedia.org/wiki/INI_file), quindi l'ordine degli
argomenti dentro a sezione è irrilevante. Il file di configurazione
viene letto da una libreria di \"*terza parte*\" ottenuta da
[rxi/ini](https://github.com/rxi/ini). Si sarebbe potuto usare la
libreria [Glib](https://gitlab.gnome.org/GNOME/glib) tuttavia si è
riscontrata una memory leak con valgrind.
La libreria threadpool e la libreria conn sono state tratte dalla
soluzione dell'esercitazione 11 pubblicata sulla pagina del corso. Si è
preferito l'utilizzo di `strsep` invece di `strtok` o `strtok_r` sia nel
programma server che client. Il codice è stato adattato leggermente da
quello presente nella libreria C GNU
([strsep](https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strsep.c;h=b534a1ec17fd2e91087af04abe1c3f1ac3e74ce0;hb=HEAD))
in modo da utilizzare la funzione omonima su piattaforme BSD e MacOS.
## Main
Il thread principale accetta le connessioni in arrivo da i client su un
socket di tipo `AF_UNIX` con nome di default *socket*. Viene interrotto
dal thread `sighandler_thread` se ricevuto un segnale fra: `SIGHUP`,
`SIGINT` o `SIGQUIT`.
## serverWorker
Il thread main dopo aver accettato la connessione di un client crea un
thread \"*worker*\" che aggiunge alla threadpool (definita nei file
omonimi). Ogni worker serve una singola richiesta da parte di un
qualsiasi client connesso. Se le richieste contemporanee dei client
fossero maggiori della dimensione della threadpool, le richieste vengono
accodate in una *pending queue* di dimensione stabilita nel file di
configurazione del server. La singola richiesta viene letta dal worker
thread e viene poi eseguita la funzione associata alla richiesta
presente nella libreria *apiFile*. Alcune di queste operazioni possono
richiedere la presa di una lock associata a un file, quindi per gestire
queste richieste si è fatto uso di una struttura di dati interna
`waiting_t`. La richiesta accodata non è quindi gestita dal thread
corrente, ma dal thread che rilascerà la lock al file associato. Si
evitano quindi *polling* o l'utilizzo di *condition*.
## sighandler_thread
Il thread gestisce i segnali in arrivo al server. Se il segnale ricevuto
è di tipo `SIGINT` o `SIGQUIT`, il server termina prima possibile. Se il
segnale ricevuto è del tipo `SIGHUP` invece si attende che ogni worker
abbiano servito le richieste dei client precedentemente connessi.
# Storage
Il server gestisce la memorizzazione dei file tramite la libreria
*fileQueue*. Nel header file *fileQueue.h* la costante `ALGORITHM`
imposta la politica di rimpiazzamento. Le funzioni che differiscono in
comportamento sono: `dequeueN`, `lockFileInQueue`, `unlockFileInQueue`,
`openFileInQueue`, `find`, `request`, `searchFile`. Infatti l'elemento
che richiedono come input viene accodato alla fine della struttura nella
politica LRU. Le altre funzioni invece non modificano l'ordine degli
elementi.
La struttura principale è una linked list chiamata `nodeT` che contiene
i dati del file associato e un puntatore al prossimo elemento. Dato che
l'operazione di scrittura di un file in memoria non è atomica, si è
deciso di usare due misure dell'occupazione della memoria di un file:
`size` e `valid`. L'unico momento in cui potrebbero essere differenti è
quando il client ha richiesto la scrittura di dati, comunicando la
dimensione, quindi il server \"alloca\" tale dimensione come `size`,
lasciando invariato la dimensione `valid`. Dopo che il client ha inviato
i dati da inserire nel file, le dimensioni dovrebbero di nuovo
combaciare. Una singola operazione di scrittura può richiedere più di un
file rimosso per aver abbastanza memoria libera; questa operazione viene
attuata atomicamente e viene considerata come un'unica operazione di
rimpiazzamento.
# Logging
Il server usa la libreria \"`taglialegna`\" per scrivere i messaggi di
log su un file specificato nel file di configurazione. Le scritture
vengono sincronizzate tramite lock. Sono state scritte due funzioni che
permettono la scrittura: `taglia_write`, `taglia_log`. `taglia_log`
scrive anche un timestamp prima del messaggio di log. All'inizio
dell'esecuzione del server vengono stampate alcune informazioni lette
dal file di configurazione, come ad esempio la dimensione del threadpool
o la dimensione massima della struttura di cache del server. Al termine
vengono invece scritti alcuni dati di sunto delle operazioni, ad esempio
il numero massimo di file memorizzati o la dimensione massima raggiunta.
# Client
Oltre alle funzioni di API richieste dalla specifica è stata
implementata la funzione `setDirectory`, perchè si è interpretata come
se le opzioni `-d` e `-D` dovessero poter essere associate a più di un
comando. Tuttavia questa funzionalità non è presente nel client in modo
diretto dato che vengono inserite `-d`/`-D /dev/null` fra due opzioni
`-w`/`-W` o `-r`/`-R` successive. L'opzione `-d /dev/null` o
`-D /dev/null` è stata ottimizzata leggermente in modo da non fare
chiamate di sistema.\
Il client esegue prima l'opzione `-f`, in seguito l'opzione `-t`, poi in
ordine tutti gli altri argomenti. In caso di errore durante la scrittura
su un file dopo la creazione, il client richiede la rimozione del file.\
Per la comunicazione con il server si è optato per un protocollo
ispirato a quello html o ftp, costituito da due cifre, la prima denota
il risultato (positive completion/transient negative
completion/permanent negative completion), la seconda invece è una
specifica della prima. Non tutte le combinazioni sono state usate.
::: center
Number Meaning
---------------------------- ------------------------------------------------------------
2[\*]{style="color: gray"} positive completion reply
4[\*]{style="color: gray"} transient negative completion reply (command not executed)
5[\*]{style="color: gray"} permanent negative completion reply (errors)
[\*]{style="color: gray"}0 syntax
[\*]{style="color: gray"}1 information
[\*]{style="color: gray"}2 connection
[\*]{style="color: gray"}5 file system
:::
Di seguito una tabella in cui si associa ogni numero al codice usato:
::: center
Number Meaning Code
--------------------------- --------------------------------- ----------------------------
20 OK `MEOK`
[21]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
[22]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
25 file purged `MEFP`
[40]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
[41]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
42 shutting down `MESD`
45 requested file action not taken `MENT`
50 syntax error `MESY`
[51]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
[52]{style="color: gray"} [not used]{style="color: gray"} [`-`]{style="color: gray"}
55 server error `MESE`
:::
Il client invia per primo la dimensione del messaggio come `long`, poi
il messaggio. Il server risponde prima con due caratteri che
simboleggiano is response number, seguito dal errno se si è riscontrato
un errore, oppure i file richiesti o espulsi. Il server per inviare i
file richiesti o espulsi, prima invia il numero di file, poi per ogni
file la dimensione come `int64_t` e poi successivamente il file.

BIN
Relazione/Relazione.pdf Normal file

Binary file not shown.

View File

@ -14,14 +14,14 @@
\usepackage{listings} %% code block
\lstset{
language=C,
showspaces=false,
basicstyle=\small\ttfamily,
numbers=left,
numberstyle=\tiny,
breaklines=true,
postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space},
backgroundcolor = \color{lightgray},
language=C,
showspaces=false,
basicstyle=\small\ttfamily,
numbers=left,
numberstyle=\tiny,
breaklines=true,
postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space},
backgroundcolor = \color{lightgray},
}
\usepackage{enumitem} %% for lists and enumerating
@ -61,16 +61,16 @@
%% usage: \ieb{Class:} for simple item
%% \ieb[4cm]{Class:} for specific size of box
\newcommand{\ieb}[2][2cm]{
\makebox[#1][l]{\emph{#2}}
\makebox[#1][l]{\emph{#2}}
} %% TODO: replace with description environment (? maybe)
%---------------------------------%
\title{Progetto di Laboratorio di Sistemi Operativi}
\author{
Elvis Rossi - Matricola 561394
Elvis Rossi - Matricola 561394
}
\date{\today}
\date{May 20, 2022}
%---------------------------------%
@ -85,14 +85,14 @@
\section{Introduzione}
Il progetto realizzato consiste in un file storage gestito da un server multithreaded e un client che possono comunicarsi attraverso le funzioni implementate dall'API.
Il codice è presente sulla repository privata al link: \href{http://google.com}{TODO}.\\
Il codice è presente sulla repository privata al link: \href{http://tautocrono.it}{tautocrono.it}.\\
Si può accedere usando le credenziali:
\begin{center}
\begin{tabular}{||c|c||}
\hline
\hline
Username & Password \\ \hline
Guest & CNs7Vbx3vLJQ2aK \\ \hline
Guest & \\ \hline
\end{tabular}
\end{center}
@ -101,15 +101,21 @@ Ci sono anche i target clean e cleanall che rimuovono gli eseguibili creati con
Sono inclusi anche dei file per testare le funzionalità del client e del server nella cartella \textit{testFiles}.
Gli oggetti vengono creati nella directory \textit{obj/}, mentre gli eseguibili vengono creati nella directory \textit{build/}.
La directory \textit{src/} contiene il codice che fa parte del main del server e del client; il resto del codice è contenuto nella directory \textit{lib/}.
È stata implementata sia la politica di rimpiazzamento FIFO che la politica LRU.
%% ------------------------- %%
\section{Server}
Il server prende come unico argomento il path al file di configurazione. Un esempio di file di configurazione è \texttt{config.ini} presente nella root del progetto. La struttura del file è analoga a quella di un file \href{https://en.wikipedia.org/wiki/INI_file}{INI}, quindi l'ordine degli argomenti è irrilevante.
Il file di configurazione viene letto da una libreria di "\textit{terza parte}" ottenuta da \href{https://github.com/rxi/ini}{rxi/ini}.
Il server prende come unico argomento il path al file di configurazione. Un esempio di file di configurazione è \texttt{config.ini} presente nella root del progetto. La struttura del file è analoga a quella di un file \href{https://en.wikipedia.org/wiki/INI_file}{INI}, quindi l'ordine degli argomenti dentro a sezione è irrilevante.
Il file di configurazione viene letto da una libreria di "\textit{terza parte}" ottenuta da \href{https://github.com/rxi/ini}{rxi/ini}. Si sarebbe potuto usare la libreria \href{https://gitlab.gnome.org/GNOME/glib}{Glib} tuttavia si è riscontrata una memory leak con valgrind.
La libreria threadpool e la libreria conn sono state tratte dalla soluzione dell'esercitazione 11 pubblicata sulla pagina del corso.
Si è preferito l'utilizzo di \texttt{strsep} invece di \texttt{strtok} o \texttt{strtok\_r} sia nel programma server che client.
Il codice è stato adattato leggermente da quello presente nella libreria C GNU (\href{https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strsep.c;h=b534a1ec17fd2e91087af04abe1c3f1ac3e74ce0;hb=HEAD}{strsep}) in modo da utilizzare la funzione omonima su piattaforme BSD.
Il codice è stato adattato leggermente da quello presente nella libreria C GNU (\href{https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strsep.c;h=b534a1ec17fd2e91087af04abe1c3f1ac3e74ce0;hb=HEAD}{strsep}) in modo da utilizzare la funzione omonima su piattaforme BSD e MacOS.
\subsection{Main}
@ -134,8 +140,10 @@ Se il segnale ricevuto è del tipo \texttt{SIGHUP} invece si attende che ogni wo
\section{Storage}
Il server gestisce la memorizzazione dei file tramite la libreria \textit{fileQueue}.
Nel header file \textit{fileQueue.h} la costante \texttt{ALGORITHM} imposta la politica di rimpiazzamento. Le funzioni che differiscono in comportamento sono: \texttt{dequeueN}, \texttt{lockFileInQueue}, \texttt{unlockFileInQueue}, \texttt{openFileInQueue}, \texttt{find}, \texttt{request}, \texttt{searchFile}. Infatti l'elemento che richiedono come input viene accodato alla fine della struttura nella politica LRU. Le altre funzioni invece non modificano l'ordine degli elementi.
La struttura principale è una linked list chiamata \texttt{nodeT} che contiene i dati del file associato e un puntatore al prossimo elemento.
Dato che l'operazione di scrittura di un file in memoria non è atomica, si è deciso di usare due misure dell'occupazione della memoria da un file: \texttt{size} e \texttt{valid}.
Dato che l'operazione di scrittura di un file in memoria non è atomica, si è deciso di usare due misure dell'occupazione della memoria di un file: \texttt{size} e \texttt{valid}.
L'unico momento in cui potrebbero essere differenti è quando il client ha richiesto la scrittura di dati, comunicando la dimensione, quindi il server "alloca" tale dimensione come \texttt{size}, lasciando invariato la dimensione \texttt{valid}. Dopo che il client ha inviato i dati da inserire nel file, le dimensioni dovrebbero di nuovo combaciare.
Una singola operazione di scrittura può richiedere più di un file rimosso per aver abbastanza memoria libera; questa operazione viene attuata atomicamente e viene considerata come un'unica operazione di rimpiazzamento.
@ -149,7 +157,7 @@ Le scritture vengono sincronizzate tramite lock.
Sono state scritte due funzioni che permettono la scrittura: \texttt{taglia\_write}, \texttt{taglia\_log}.
\texttt{taglia\_log} scrive anche un timestamp prima del messaggio di log.
All'inizio dell'esecuzione del server vengono stampate alcune informazioni lette dal file di configurazione, come ad esempio la dimensione del threadpool o la dimensione massima della struttura di cache del server.
Al termine vengono invece scritti alcuni dati di sunto delle operazioni, come ad esempio il numero massimo di file memorizzati o la dimensione massima raggiunta.
Al termine vengono invece scritti alcuni dati di sunto delle operazioni, ad esempio il numero massimo di file memorizzati o la dimensione massima raggiunta.
%% ------------------------- %%
@ -158,6 +166,11 @@ Al termine vengono invece scritti alcuni dati di sunto delle operazioni, come ad
Oltre alle funzioni di API richieste dalla specifica è stata implementata la funzione \texttt{setDirectory}, perchè si è interpretata come se le opzioni \texttt{-d} e \texttt{-D} dovessero poter essere associate a più di un comando. Tuttavia questa funzionalità non è presente nel client in modo diretto dato che vengono inserite \texttt{-d}/\texttt{-D /dev/null} fra due opzioni \texttt{-w}/\texttt{-W} o \texttt{-r}/\texttt{-R} successive.
L'opzione \texttt{-d /dev/null} o \texttt{-D /dev/null} è stata ottimizzata leggermente in modo da non fare chiamate di sistema.
\\
Il client esegue prima l'opzione \texttt{-f}, in seguito l'opzione \texttt{-t}, poi in ordine tutti gli altri argomenti.
In caso di errore durante la scrittura su un file dopo la creazione, il client richiede la rimozione del file.
\\
Per la comunicazione con il server si è optato per un protocollo ispirato a quello html o ftp, costituito da due cifre, la prima denota il risultato (positive completion/transient negative completion/permanent negative completion), la seconda invece è una specifica della prima. Non tutte le combinazioni sono state usate.
@ -180,23 +193,23 @@ Di seguito una tabella in cui si associa ogni numero al codice usato:
\begin{center}
\begin{tabular}{|c|l|c|}
\hline
Number & Meaning & Code \\ \hline
\hline
\hline
Number & Meaning & Code \\ \hline
\hline
20 & OK & \texttt{MEOK} \\ \hline
\gry{21} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{22} & \gry{not used} & \gry{\texttt{-}} \\ \hline
25 & file purged & \texttt{MEFP} \\ \hline
\hline
\gry{40} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{41} & \gry{not used} & \gry{\texttt{-}} \\ \hline
42 & shutting down & \texttt{MESD} \\ \hline
45 & requested file action not taken & \texttt{MENT} \\ \hline
\hline
50 & syntax error & \texttt{MESY} \\ \hline
\gry{51} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{52} & \gry{not used} & \gry{\texttt{-}} \\ \hline
55 & server error & \texttt{MESE} \\ \hline
\gry{21} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{22} & \gry{not used} & \gry{\texttt{-}} \\ \hline
25 & file purged & \texttt{MEFP} \\ \hline
\hline
\gry{40} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{41} & \gry{not used} & \gry{\texttt{-}} \\ \hline
42 & shutting down & \texttt{MESD} \\ \hline
45 & requested file action not taken & \texttt{MENT} \\ \hline
\hline
50 & syntax error & \texttt{MESY} \\ \hline
\gry{51} & \gry{not used} & \gry{\texttt{-}} \\ \hline
\gry{52} & \gry{not used} & \gry{\texttt{-}} \\ \hline
55 & server error & \texttt{MESE} \\ \hline
\end{tabular}
\end{center}
@ -205,55 +218,7 @@ Il server risponde prima con due caratteri che simboleggiano is response number,
Il server per inviare i file richiesti o espulsi, prima invia il numero di file, poi per ogni file la dimensione come \texttt{int64\_t} e poi successivamente il file.
%% ------------------------- %%
\end{document}

View File

@ -666,6 +666,15 @@ int writeFile(const char* pathname, const char* dirname) {
}
}
}
freeResponse(res);
free(res);
res = calloc(1, sizeof(response_t));
if(!res){
perror("writeFile: calloc");
free(content);
free(cmd);
return -1;
}
/* send file to server */
if (writen(fd_skt, content, size) == -1) {
@ -677,7 +686,6 @@ int writeFile(const char* pathname, const char* dirname) {
return -1;
}
/* reciveData(, 0) because if we get a MEOK we don't expect files */
if(reciveData(res, 0) < 0) {
/* errno is set by reciveData */

View File

@ -381,7 +381,7 @@ void readFile(char *filepath, queueT *q, long fd_c, taglia_t *taglia) {
}
if(f->open == 0) { /* file not already open */
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readFile sul file \"%s\" e' terminata con errore, file non aperto\n",
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readFile sul file \"%s\" e' terminata con errore, file non precedentemente aperto\n",
fd_c,
filepath);
errno = EPERM;
@ -417,32 +417,33 @@ void readNFiles(int num, queueT *q, long fd_c, taglia_t *taglia) {
int oldNum = num;
int64_t ntosend = (num<=0 || num>getLen(q))? getLen(q) : num;
fileT **toSend = calloc(ntosend, sizeof(fileT *));
fileT **toSend = NULL;
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readNFile (n = %d) e' terminata con successo. File inviati:\n",
fd_c,
num);
/* extract n files, we dont check if the client can actually modify
* the files since we add them back immediatly */
/* extract n files, but dont actually dequeue them */
toSend = request(q, (int) ntosend);
if(toSend == NULL) {
n = 0;
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readNFiles (n = %d) e' terminato con errore del server\n",
fd_c,
oldNum);
perror("readNFiles: request");
serror(MESE, fd_c, taglia, tmp_buf);
return;
}
size_t tot = 0;
for(int i=0;i<ntosend;++i) {
toSend[i] = dequeue(q);
if(!toSend[i]) {
if(toSend[i] == NULL) {
n = 0;
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readNFiles (n = %d) e' terminato con errore del server\n",
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readNFiles (n = %d) e' terminato con errore del server (file estratti [%d] inferiori a file richiesti [%ld])\n",
fd_c,
oldNum);
perror("readNFiles: dequeue");
serror(MESE, fd_c, taglia, tmp_buf);
return;
}
int tmp = enqueue(q, toSend[i]);
if(tmp<0) {
n = 0;
n += snprintf(tmp_buf+n, m-n, "Client %ld ha richiesto una readNFiles (n = %d) e' terminato con errore del server\n",
fd_c,
oldNum);
perror("readNFiles: enqueue");
oldNum,
i,
ntosend);
perror("readNFiles: request");
serror(MESE, fd_c, taglia, tmp_buf);
return;
}
@ -452,7 +453,7 @@ void readNFiles(int num, queueT *q, long fd_c, taglia_t *taglia) {
toSend[i]->filepath,
toSend[i]->valid);
}
n += snprintf(tmp_buf+n, m-n, "readNFile dimensione totale = %ld\n",
n += snprintf(tmp_buf+n, m-n, "readNFile dimensione totale = %ld B\n",
tot);
sendMessageFileN(MEOK, toSend, ntosend, fd_c, taglia, tmp_buf);

View File

@ -129,7 +129,7 @@ int enqueue(queueT *q, fileT* data) {
goto _end_enqueue;
}
/* insert the element */
/* insert the element at the end */
nodeT *newNode = malloc(sizeof(nodeT));
if (newNode == NULL) {
perror("enqueue: malloc");
@ -178,6 +178,7 @@ fileT* dequeue(queueT *q) {
fileT *data = (q->head)->data;
/* dequeue the first in the list */
nodeT *tmp = NULL;
tmp = q->head;
q->head = (q->head)->next;
@ -198,7 +199,6 @@ _end_dequeue:
return NULL;
}
/* dequeue until we have s free space and expand the file by s */
fileT ** dequeueN(queueT *q, char *filepath, size_t s) {
if(!q) {
@ -211,22 +211,33 @@ fileT ** dequeueN(queueT *q, char *filepath, size_t s) {
LOCK_RETURN(&q->m, NULL); /* begin me */
fileT **returnList = NULL; /* list of removed files */
if (q->head == NULL || q->len == 0) { /* empty queue */
errno = ENOENT;
goto _end_dequeueN;
}
if(q->maxSize > s) {
if(q->maxSize < s) {
errno = EINVAL;
goto _end_dequeueN;
}
/* try to find the file */
nodeT *tmp = q->head;
/* if LRU keep track of previous */
#if ALGORITHM == 1
nodeT *prev = NULL;
#endif
while (tmp) {
if (strcmp(filepath, (tmp->data)->filepath) == 0) { /* found */
break;
}
/* if LRU keep track of previous */
#if ALGORITHM == 1
prev = tmp;
#endif
tmp = tmp->next;
}
@ -235,36 +246,80 @@ fileT ** dequeueN(queueT *q, char *filepath, size_t s) {
goto _end_dequeueN;
}
fileT **returnList = NULL; /* list of removed files */
tmp = NULL;
nodeT* file = tmp;
returnList = calloc(1, sizeof(fileT*));
returnList[0] = NULL;
/* if LRU put the file at the end */
#if ALGORITHM == 1
if(prev==NULL && tmp->next==NULL) {
q->head = tmp;
q->tail = tmp;
} else if(prev==NULL) {
q->head = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
} else if(tmp->next!=NULL) {
prev->next = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
}
#endif /* ALGORITHM == 1 */
tmp = q->head;
returnList = calloc(1, sizeof(*returnList));
if(!returnList) {
perror("dequeueN: calloc");
goto _end_dequeueN;
}
int purged = 0;
int addFileAtEnd = 0;
while(q->size + s > q->maxSize) {
purged++;
if (q->head == NULL) { /* queue becomes empty */
q->tail = NULL;
break; /* we eliminated everything so we must have enought space */
}
if(q->head == file) { /* dont delete the file to expand */
q->head = q->head->next;
addFileAtEnd = 1;
}
tmp = q->head;
q->head = q->head->next;
++purged;
returnList = realloc(returnList, purged * sizeof(fileT*));
if(!returnList) {
perror("dequeueN: realloc");
goto _end_dequeueN;
}
tmp = q->head;
q->head = (q->head)->next;
returnList[purged-1] = tmp->data;
--q->len;
q->size -= tmp->data->size;
if (q->head == NULL) { /* queue becomes empty */
q->tail = NULL;
break; /* we eliminated everything so we must have enought space */
free(tmp); /* free of the node */
}
if(addFileAtEnd == 1) {
/* if LRU, we know already that this code is unrechable
* and that q->head must be NULL if rechable */
if(q->head == NULL) {
q->head = file;
q->head->next = NULL;
} else {
file->next = q->head;
q->head = file;
}
}
returnList = realloc(returnList, (purged+1) * sizeof(fileT*));
if(!returnList) {
perror("dequeueN: realloc");
goto _end_dequeueN;
}
returnList[purged] = NULL; /* null terminated */
tmp->data->size += s;
file->data->size += s;
q->size += s;
UNLOCK_RETURN(&q->m, NULL); /* end me */
@ -272,6 +327,8 @@ fileT ** dequeueN(queueT *q, char *filepath, size_t s) {
_end_dequeueN:
UNLOCK_RETURN(&q->m, NULL);
if(returnList)
free(returnList);
return NULL;
}
@ -351,11 +408,19 @@ int lockFileInQueue(queueT *q, char *filepath, int owner) {
/* find file */
nodeT *tmp = q->head;
/* if LRU keep track of previous */
#if ALGORITHM == 1
nodeT *prev = NULL;
#endif
while (tmp) {
if (strcmp(filepath, (tmp->data)->filepath) == 0) { /* found */
break;
}
/* if LRU keep track of previous */
#if ALGORITHM == 1
prev = tmp;
#endif
tmp = tmp->next;
}
@ -374,6 +439,25 @@ int lockFileInQueue(queueT *q, char *filepath, int owner) {
(tmp->data)->O_LOCK = 1;
(tmp->data)->owner = owner;
/* if LRU put the file at the end */
#if ALGORITHM == 1
if(prev==NULL && tmp->next==NULL) {
q->head = tmp;
q->tail = tmp;
} else if(prev==NULL) {
q->head = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
} else if(tmp->next!=NULL) {
prev->next = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
}
#endif /* ALGORITHM == 1 */
UNLOCK_RETURN(&q->m, -1); /* end me */
return 0;
@ -449,10 +533,19 @@ int openFileInQueue(queueT *q, char *filepath, int O_LOCK, int owner) {
/* find the file */
nodeT *tmp = q->head;
/* if LRU keep track of previous */
#if ALGORITHM == 1
nodeT *prev = NULL;
#endif
while (tmp) {
if (strcmp(filepath, (tmp->data)->filepath) == 0) { /* found */
break;
}
/* if LRU keep track of previous */
#if ALGORITHM == 1
prev = tmp;
#endif
tmp = tmp->next;
}
@ -474,6 +567,24 @@ int openFileInQueue(queueT *q, char *filepath, int O_LOCK, int owner) {
(tmp->data)->owner = owner;
}
/* if LRU put the file at the end */
#if ALGORITHM == 1
if(prev==NULL && tmp->next==NULL) {
q->head = tmp;
q->tail = tmp;
} else if(prev==NULL) {
q->head = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
} else if(tmp->next!=NULL) {
prev->next = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
}
#endif /* ALGORITHM == 1 */
UNLOCK_RETURN(&q->m, -1); /* end me */
return 0;
@ -545,11 +656,6 @@ int writeFileInQueue(queueT *q, char *filepath, void *data, size_t size, int own
goto _end_write_file_queue;
}
if (q->size + size > q->maxSize) { /* not enough space */
errno = EFBIG;
goto _end_write_file_queue;
}
/* find the file */
nodeT *tmp = q->head;
while (tmp) {
@ -564,6 +670,11 @@ int writeFileInQueue(queueT *q, char *filepath, void *data, size_t size, int own
goto _end_write_file_queue;
}
if (q->size - tmp->data->size + size > q->maxSize) { /* not enough space */
errno = EFBIG;
goto _end_write_file_queue;
}
if ((tmp->data)->open == 0 || ((tmp->data)->O_LOCK && (tmp->data)->owner != owner)) {
/* file is closed or the client does not hold the lock */
errno = EPERM;
@ -605,11 +716,6 @@ int appendFileInQueue(queueT *q, char *filepath, void *data, size_t size, int ow
goto _end_append_file_queue;
}
if (q->size + size > q->maxSize) { /* not enough space */
errno = EFBIG;
goto _end_append_file_queue;
}
/* find the file */
nodeT *tmp = q->head;
while (tmp) {
@ -624,6 +730,11 @@ int appendFileInQueue(queueT *q, char *filepath, void *data, size_t size, int ow
goto _end_append_file_queue;
}
if (q->size - tmp->data->size + size > q->maxSize) { /* not enough space */
errno = EFBIG;
goto _end_append_file_queue;
}
if ((tmp->data)->open == 0 || ((tmp->data)->O_LOCK && (tmp->data)->owner != owner)) {
/* file is closed or the client does not hold the lock */
errno = EPERM;
@ -732,11 +843,19 @@ fileT* find(queueT *q, char *filepath) {
fileT *res = NULL;
nodeT *tmp = q->head;
/* if LRU keep track of previous */
#if ALGORITHM == 1
nodeT *prev = NULL;
#endif
while (tmp) {
if (strcmp(filepath, (tmp->data)->filepath) == 0) { /* trovato */
break;
}
/* if LRU keep track of previous */
#if ALGORITHM == 1
prev = tmp;
#endif
tmp = tmp->next;
}
@ -757,6 +876,24 @@ fileT* find(queueT *q, char *filepath) {
goto _end_find_in_queue;
}
/* if LRU put the file at the end */
#if ALGORITHM == 1
if(prev==NULL && tmp->next==NULL) {
q->head = tmp;
q->tail = tmp;
} else if(prev==NULL) {
q->head = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
} else if(tmp->next!=NULL) {
prev->next = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
}
#endif /* ALGORITHM == 1 */
UNLOCK_RETURN(&q->m, NULL); /* end me */
return res;
@ -765,6 +902,65 @@ _end_find_in_queue:
return NULL;
}
/* return n files */
fileT ** request(queueT *q, int n) {
if(!q) {
errno = EINVAL;
return NULL;
}
if(n<=0) {
return NULL;
}
LOCK_RETURN(&q->m, NULL); /* begin me */
fileT **returnList = NULL; /* list of requested files */
if (q->head == NULL || q->len == 0) { /* empty queue */
errno = ENOENT;
goto _end_request;
}
if(q->size < n) {
errno = EINVAL;
goto _end_request;
}
returnList = calloc(n+1, sizeof(*returnList));
if(!returnList) {
perror("dequeueN: calloc");
goto _end_request;
}
returnList[n] = NULL;
nodeT *tmp = q->head;
for(int i=0; i<n; ++i) {
if(tmp == NULL) {
errno = ENOENT;
goto _end_request;
}
returnList[i] = tmp->data;
tmp = tmp->next;
}
/* if LRU put the files at the end */
#if ALGORITHM == 1
q->tail->next = q->head;
q->head = tmp->next;
tmp->next = NULL;
q->tail = tmp;
#endif /* ALGORITHM == 1 */
UNLOCK_RETURN(&q->m, NULL); /* end me */
return returnList;
_end_request:
UNLOCK_RETURN(&q->m, NULL);
if(returnList)
free(returnList);
return NULL;
}
/* search for file and return result of search */
int searchFile(queueT *q, char *filepath) {
if(!q || !filepath) {
@ -778,17 +974,43 @@ int searchFile(queueT *q, char *filepath) {
goto _end_search_file_in_queue;
nodeT *tmp = q->head;
/* if LRU keep track of previous */
#if ALGORITHM == 1
nodeT *prev = NULL;
#endif
while (tmp) {
if (strcmp(filepath, (tmp->data)->filepath) == 0) { /* found */
break;
}
/* if LRU keep track of previous */
#if ALGORITHM == 1
prev = tmp;
#endif
tmp = tmp->next;
}
if(!tmp)
goto _end_search_file_in_queue;
/* if LRU put the file at the end */
#if ALGORITHM == 1
if(prev==NULL && tmp->next==NULL) {
q->head = tmp;
q->tail = tmp;
} else if(prev==NULL) {
q->head = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
} else if(tmp->next!=NULL) {
prev->next = tmp->next;
q->tail->next = tmp;
q->tail = tmp;
tmp->next = NULL;
}
#endif /* ALGORITHM == 1 */
UNLOCK_RETURN(&q->m, 0); /* end me */
return 1;
@ -840,6 +1062,9 @@ void destroyQueue(queueT *q) {
if (errno) {
perror("destroyQueue: voidDequeue");
}
if(q->head == NULL) {
break;
}
}
pthread_mutex_destroy(&q->m);

View File

@ -7,6 +7,12 @@
#include "conn.h"
/* Algoritmo che viene usato per il rimpiazzo dei dati:
se = 0 -> FIFO
se = 1 -> LRU
*/
#define ALGORITHM 0
/* struttura dati per gestire i file in memoria principale */
typedef struct {
char *filepath; /* path assoluto del file */
@ -67,8 +73,7 @@ void destroyFile(fileT *f);
/**
* Alloca ed inizializza una coda di fileT. Deve essere chiamata da un solo
* thread.
* Alloca ed inizializza una coda di fileT.
* @param maxLen lunghezza massima della coda
* @param maxSize dimensione massima della coda in bytes
*
@ -215,6 +220,15 @@ int removeFileFromQueue(queueT *q, char *filepath, int owner);
*/
fileT* find(queueT *q, char *filepath);
/**
* Estrae n fileT dalla coda senza eliminarli
* @param q puntatore alla coda
* @param n numero di file da estrarre
*
* @return puntatore i file estratti (NULL terminated), NULL se errore
*/
fileT ** request(queueT *q, int n);
/**
* Cerca il fileT nella coda e ritorna il siultato dell'operazione
* @param q puntatore alla coda sulla quale cercare il fileT

View File

@ -17,7 +17,7 @@
* @function void *threadpool_thread(void *threadpool)
* @brief funzione eseguita dal thread worker che appartiene al pool
*/
static void *workerpool_thread(void *threadpool) {
static void *workerpool_thread(void *threadpool) {
threadpool_t *pool = (threadpool_t *)threadpool; // cast
taskfun_t task; // generic task
pthread_t self = pthread_self();
@ -34,28 +34,28 @@ static void *workerpool_thread(void *threadpool) {
LOCK_RETURN(&(pool->lock), NULL);
for (;;) {
// in attesa di un messaggio, controllo spurious wakeups.
while((pool->count == 0) && (!pool->exiting)) {
pthread_cond_wait(&(pool->cond), &(pool->lock));
// in attesa di un messaggio, controllo spurious wakeups.
while((pool->count == 0) && (!pool->exiting)) {
pthread_cond_wait(&(pool->cond), &(pool->lock));
}
if (pool->exiting > 1) break; // exit forzato, esco immediatamente
// devo uscire ma ci sono messaggi pendenti
if (pool->exiting == 1 && !pool->count) break;
if (pool->exiting > 1) break; // exit forzato, esco immediatamente
// devo uscire ma ci sono messaggi pendenti
if (pool->exiting == 1 && !pool->count) break;
// nuovo task
task.fun = pool->pending_queue[pool->head].fun;
task.arg = pool->pending_queue[pool->head].arg;
task.fun = pool->pending_queue[pool->head].fun;
task.arg = pool->pending_queue[pool->head].arg;
pool->head++; pool->count--;
pool->head = (pool->head == abs(pool->queue_size)) ? 0 : pool->head;
pool->head++; pool->count--;
pool->head = (pool->head == abs(pool->queue_size)) ? 0 : pool->head;
pool->taskonthefly++;
UNLOCK_RETURN(&(pool->lock), NULL);
UNLOCK_RETURN(&(pool->lock), NULL);
// eseguo la funzione
(*(task.fun))(task.arg);
// eseguo la funzione
(*(task.fun))(task.arg);
LOCK_RETURN(&(pool->lock), NULL);
pool->taskonthefly--;
}
@ -69,22 +69,22 @@ static void *workerpool_thread(void *threadpool) {
static int freePoolResources(threadpool_t *pool) {
if(pool->threads) {
free(pool->threads);
free(pool->pending_queue);
pthread_mutex_destroy(&(pool->lock));
pthread_cond_destroy(&(pool->cond));
free(pool->threads);
free(pool->pending_queue);
pthread_mutex_destroy(&(pool->lock));
pthread_cond_destroy(&(pool->cond));
}
free(pool);
free(pool);
return 0;
}
threadpool_t *createThreadPool(int numthreads, int pending_size) {
if(numthreads <= 0 || pending_size < 0) {
errno = EINVAL;
return NULL;
return NULL;
}
threadpool_t *pool = (threadpool_t *)malloc(sizeof(threadpool_t));
if (pool == NULL) return NULL;
@ -115,20 +115,20 @@ threadpool_t *createThreadPool(int numthreads, int pending_size) {
return NULL;
}
for(int i = 0; i < numthreads; i++) {
if(pthread_create(&(pool->threads[i]), NULL,
workerpool_thread, (void*)pool) != 0) {
if(pthread_create(&(pool->threads[i]), NULL,
workerpool_thread, (void*)pool) != 0) {
/* errore fatale, libero tutto forzando l'uscita dei threads */
destroyThreadPool(pool, 1);
destroyThreadPool(pool, 1);
errno = EFAULT;
return NULL;
}
pool->numthreads++;
return NULL;
}
pool->numthreads++;
}
return pool;
}
int destroyThreadPool(threadpool_t *pool, int force) {
int destroyThreadPool(threadpool_t *pool, int force) {
if(pool == NULL || force < 0) {
errno = EINVAL;
return -1;
@ -171,30 +171,30 @@ int addToThreadPool(threadpool_t *pool, void (*f)(void *), void *arg) {
UNLOCK_RETURN(&(pool->lock),-1);
return 1; // esco con valore "coda piena"
}
if (pool->taskonthefly >= pool->numthreads) {
if (nopending) {
if (nopending) {
// tutti i thread sono occupati e non si gestiscono task pendenti
assert(pool->count == 0);
UNLOCK_RETURN(&(pool->lock),-1);
return 1; // esco con valore "coda piena"
}
}
}
pool->pending_queue[pool->tail].fun = f;
pool->pending_queue[pool->tail].arg = arg;
pool->count++;
pool->count++;
pool->tail++;
if (pool->tail >= queue_size) pool->tail = 0;
int r;
if((r=pthread_cond_signal(&(pool->cond))) != 0) {
UNLOCK_RETURN(&(pool->lock),-1);
errno = r;
return -1;
}
UNLOCK_RETURN(&(pool->lock),-1);
return 0;
}
@ -204,11 +204,11 @@ int addToThreadPool(threadpool_t *pool, void (*f)(void *), void *arg) {
* @function void *thread_proxy(void *argl)
* @brief funzione eseguita dal thread worker che non appartiene al pool
*/
static void *proxy_thread(void *arg) {
static void *proxy_thread(void *arg) {
taskfun_t *task = (taskfun_t*)arg;
// eseguo la funzione
// eseguo la funzione
(*(task->fun))(task->arg);
free(task);
return NULL;
}
@ -220,7 +220,7 @@ int spawnThread(void (*f)(void*), void* arg) {
return -1;
}
taskfun_t *task = malloc(sizeof(taskfun_t)); // la memoria verra' liberata dal proxy
taskfun_t *task = malloc(sizeof(taskfun_t)); // la memoria verra' liberata dal proxy
if (!task) return -1;
task->fun = f;
task->arg = arg;

View File

View File

@ -415,7 +415,7 @@ int execute(cmd_t *l, int print) {
interval.tv_sec = (num/1000);
if (print)
fprintf(stdout, "t - Tempo fra due richieste: %ld ms\tEsito: ok\n", num);
fprintf(stdout, "t - Tempo fra due richieste: %ld ms [Esito: ok]\n", num);
tmp = NULL;
break;

View File

@ -209,7 +209,7 @@ int main(int argc, char *argv[]) {
if( taglia_log(taglia, buf) < 0)
goto _cleanup;
printf("File Server ready.");
printf("File Server ready.\n");
fflush(stdout);