Precedente Successivo

I sorgenti: client.c

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:

  • lettura della riga di comando ed inizializzazione del collegamento con il server;
  • chiamata della subroutine updatedir per aggiornare la directory common_dir;
  • sottofunzione rmr, chiamata ogni volta che si rende necessario cancellare un file regolare o una directory;
  • sottofunzione recv_file per la ricezione di un file regolare.
  • Inizializzazione


    #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.

    Sottofunzioni

    Ricezione di un file


    /*
     * 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.

    Rimozione ricorsiva


    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).

    Aggiornameto di una directory

    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.

    1. Se la lista non è vuota
      1. viene inviato al server il messaggio «Another file» o «Another dir», a seconda che il nome corrente, puntato da thisfile, designi rispettivamente un file regolare o una directory, e di seguito il nome vero e proprio.
      2. Se, invece, la lista è vuota, viene inviato il messaggio «End of list».
    2. Con readmsg viene letta la risposta che, a seconda del suo contenuto varia il comportamento del client:
      STAT:
      il server richiede l'invio della struttura "stat" del file regolare corrente. Se sul server il file, che ha lo stesso nome, è piú recente, si provvederà ad aggiornarlo. In ogni caso il file corrente è stato processato (thisfile++).
      NEW_DIR:
      sul server è presente una directory con un nome antecedente a quello corrente. È necessario crearla ed aggiornarla.
      NEW_FILE:
      in questo caso è un file regolare che deve venir creato.
      DIR_REMOVED, FILE_REMOVED:
      questa condizione è verificata quando è il nome sul client ad essere antecedente a quello presente sul server. Il file o la direcrtory relativa vanno rimossi.
      ENTER_DIR:
      in entrambe le liste si sta considerando una direcory. Per sapere se è aggiornata bisogna visitarla ricorsivamente.
    3. Il ciclo termina quando viene ricevuto un messaggio di «End of list», indipendentemente dal numero di file rimasti da processare.
    Può accadere che il client non abbia ancora terminato la sua lista. In questo caso tutti i file da quel punto in poi vanno rimossi.


    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("..");
    }
    

    Precedente Successivo