Una volta compreso il funzionamento del server, la struttura del client risulta triviale: il suo unico compito consiste nell'inviare un comando scelto in modo opportuno fra «Another file», «Another dir» o «End of list», ed intraprendere l'operazione dettata dalla risposta ricevuta.
Nota dolente: la cancellazione dei file e/o directory. Il
server, infatti, può anche ordinare la cancellazione di un intero
albero o, addirittura, tutta la common_dir. A questo scopo
è stata progettata la funzione rmr
che
permette di rimuovere ricorsivamente un'intera struttura di directory.
Vediamo, comunque, di analizzare in dettaglio il codice sorgente suddividendolo nei seguenti blocchi:
#include <stdio.h> /* printf(), perror() */ #include <unistd.h> /* read(), write(), chdir(), close(), gethostname() */ #include <sys/socket.h> /* socket(), connect(), listen(), accept() */ #include <sys/stat.h> /* stat() */ #include <arpa/inet.h> /* inet_ntoa(), ntohs(), htons() */ #include <string.h> /* bzero(), bcopy() */ #include <netdb.h> /* gethostbyname() */ #include <sys/dir.h> /* scandir() */ #include <stdlib.h> /* malloc() */ #include <fcntl.h> /* open() */ #include <utime.h> /* utime() */ #include "defs.h" #define DEBUG extern int select_dir(struct direct *); extern int readmsg(const int, char *, const size_t); extern int writemsg(const int, const char *, const size_t); extern int readstat(const int, struct stat *); extern int writestat(const int, const struct stat *); void recv_file(const int sock, const char *name); void rmr(const char *); void updatedir(const int, const char *); const char *PRGNAME; int main(argc, argv) int argc; char *argv[]; { int sock; char common_dir[1024]; struct sockaddr_in server; struct hostent *hp; PRGNAME = argv[0]; if (argc < 2) { fprintf(stderr, "usage: %s <server> [common_dir]\n", PRGNAME); exit(1); } if (argc == 3) strcpy(common_dir, argv[2]); else strcpy(common_dir, "."); hp = gethostbyname(argv[1]); if (hp == NULL) { fprintf(stderr, "%s: unknown host\n", argv[1]); exit(1); } bzero(&server, sizeof(server)); bcopy(hp->h_addr, &server.sin_addr, hp->h_length); server.sin_port = htons(SERVER_PORT); server.sin_family = hp->h_addrtype; sock = socket(server.sin_family, SOCK_STREAM, 0); if (sock < 0) { perror("opening socket"); exit(1); } #ifdef DEBUG printf("%s: socket opened\n", PRGNAME); printf("\tserver: %s [%s]\n", argv[1], inet_ntoa(server.sin_addr)); printf("\tport: %d\n", ntohs(server.sin_port)); #endif if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { perror("connect"); exit(1); } /* * Scan the local common directory * ordering files alphabetically */ updatedir(sock, common_dir); close(sock); exit(0); }
Dopo aver dichiarato alcune variabili utilizzate all'interno della
funzione main()
, viene analizzata la riga di comando. Se
è presente un secondo argomento, questo verrà assunto come il
percorso per la common_dir, altrimenti sarà considerata la
directory corrente.
Il primo argomento, invece, è obbligatorio ed indica il nome del
server. La funzione gethostbyname()
ritorna un puntatore ad
una struttura contenente l'indirizzo numerico del server che, assieme al
numero di porta SERVER_PORT
, permette di creare il socket
sock
di collegamento.
Superata con successo anche la fase di connessione
(connect
), è sufficiente iniziare l'aggiornamento
ricorsivo dalla directory common_dir
tramite la funzione
updatedir.
/* * Subfunctions */ void recv_file(const int sock, const char *name) { int fd, bysent = 0; char buf[BUF_SIZE]; struct stat filestat; struct utimbuf newfileutime; /* * {Creat,Overwrite} file name with data sent by server. * Change access and modification time to reflect * server values. */ readstat(sock, &filestat); #ifdef DEBUG printf("%s: Recv", PRGNAME); #endif fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, filestat.st_mode); while (bysent < filestat.st_size) { int byread = read(sock, buf, BUF_SIZE); bysent += write(fd, buf, byread); } printf(" %u bytes data file\n", bysent); close(fd); newfileutime.actime = filestat.st_atime; newfileutime.modtime = filestat.st_mtime; utime(name, &newfileutime); }
Questa subroutine non ha bisogno di molti commenti. Ricevute le
informazioni temporali del file (readstat
) ed i byte che lo
compongono (ciclo while
), è sufficiente aggiornarne la
data di di accesso (actime
) e di modifica
(modtime
) per completare il lavoro.
void rmr(const char *name) { struct stat filestat; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) return; stat(name, &filestat); if (S_ISREG(filestat.st_mode)) { if (unlink(name)) perror("removing file"); #ifdef DEBUG else printf("%s: File \"%s\" removed\n", PRGNAME, name); #endif } else if (S_ISDIR(filestat.st_mode)) { DIR *dirp = opendir(name); struct direct *entry; chdir(name); while ((entry = readdir(dirp))) rmr(entry->d_name); chdir(".."); if (rmdir(name)) perror("removing dir"); #ifdef DEBUG else printf("%s: Dir \"%s\" removed\n", PRGNAME, name); #endif } }
Questa subroutine rimuove dalla directory corrente il file regolare o
la directory indicata da name
. Poiché file e directory
sono entità diverse nel filesystem, è necessario
distinguere le operazioni che portano alla cancellazione degli uni da
quella delle altre.
In particolare non è possibile cancellare le directory "." e
"..", né rimuovere una directory non vuota. Pertanto, se il file
è regolare viene semplicemente eliminato da unlink()
,
mentre se è una directory, questa viene prima svuotata chiamando
ricorsivamente rmr
, e poi rimossa (rmdir
).
Come preannunciato il lato client è sostanzialmente semplice in quanto ogni decisione è presa dal server ed il suo compito è limitato all'esecuzione di ordini.
La funzione updatedir
viene chiamata con il percorso
dir
della directory da aggiornare nella quale ci si sposta
immediatamente (chdir
). La chiamata alla funzione
scandir
serve per costruire una lista dei file regolari e
delle directory presenti.
Ora è necessario processare ogni eventuale nome presente nella lista appena creata.
thisfile
, designi
rispettivamente un file regolare o una directory, e di seguito il
nome vero e proprio.readmsg
viene letta la risposta che, a seconda del suo
contenuto varia il comportamento del client:
STAT
:thisfile++
).
NEW_DIR
:NEW_FILE
:DIR_REMOVED
, FILE_REMOVED
:ENTER_DIR
:void updatedir(const int sock, const char *dir) { int totfile, thisfile; char msg[MSG_MAX]; struct direct **namelist; #ifdef DEBUG printf("%s: Scanning dir \"%s\"\n", PRGNAME, dir); #endif if (chdir(dir) == -1) { perror("cd'ing"); exit(1); } if ((totfile = scandir(".", &namelist, select_dir, alphasort)) == -1) { perror("Scanning dir"); exit(1); } /* * Send each entry to the server to check if they need to be * updated. */ #ifdef DEBUG printf("%s: %d files found\n", PRGNAME, totfile); #endif thisfile = 0; do { char *remotename = malloc(NAME_MAX+1); struct stat localstat, remotestat; if (thisfile < totfile) { stat(namelist[thisfile]->d_name, &localstat); if (S_ISREG(localstat.st_mode)) strcpy(msg, ANOTHER_FILE); else strcpy(msg, ANOTHER_DIR); writemsg(sock, msg, LEN_MESGS); writemsg(sock, namelist[thisfile]->d_name, sizeof(namelist[thisfile]->d_name)); } else writemsg(sock, END_LIST, LEN_MESGS); readmsg(sock, msg, LEN_MESGS); if (strcmp(msg, STAT) == 0) { writestat(sock, &localstat); readmsg(sock, msg, LEN_MESGS); if (strcmp(msg, NEWER_FILE) == 0) recv_file(sock, namelist[thisfile]->d_name); thisfile++; } else if (strcmp(msg, NEW_DIR) == 0) { readmsg(sock, remotename, NAME_MAX+1); readstat(sock, &remotestat); if (mkdir(remotename, remotestat.st_mode)) perror("making dir"); updatedir(sock, remotename); } else if (strcmp(msg, NEW_FILE) == 0) { readmsg(sock, remotename, NAME_MAX+1); recv_file(sock, remotename); } else if (strcmp(msg, DIR_REMOVED) == 0 || strcmp(msg, FILE_REMOVED) == 0) rmr(namelist[thisfile++]->d_name); else if (strcmp(msg, ENTER_DIR) == 0) updatedir(sock, namelist[thisfile++]->d_name); } while (strcmp(msg, END_LIST) != 0); for (; thisfile < totfile; thisfile++) rmr(namelist[thisfile]->d_name); chdir(".."); }