httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@hyperreal.org
Subject cvs commit: apache-2.0/mpm/src/modules/mpm/mpmt_pthread Makefile.tmpl acceptlock.c mpmt_pthread.c mpmt_pthread.h scoreboard.c http_accept.c
Date Fri, 09 Jul 1999 20:40:27 GMT
manoj       99/07/09 13:40:27

  Modified:    mpm/src/include scoreboard.h
               mpm/src/modules/mpm/mpmt_pthread Makefile.tmpl acceptlock.c
                        mpmt_pthread.c mpmt_pthread.h scoreboard.c
  Removed:     mpm/src/include http_accept.h
               mpm/src/modules/mpm/mpmt_pthread http_accept.c
  Log:
  Get rid of the accept abstraction, which seems out-of-place in the MPM
  codebase, and make the poll-accept model the only model supported. Also,
  some code was cleaned up and rewritten in the process.
  
  Revision  Changes    Path
  1.3       +0 -1      apache-2.0/mpm/src/include/scoreboard.h
  
  Index: scoreboard.h
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/include/scoreboard.h,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -d -u -r1.2 -r1.3
  --- scoreboard.h	1999/07/09 20:30:13	1.2
  +++ scoreboard.h	1999/07/09 20:40:09	1.3
  @@ -174,7 +174,6 @@
       pid_t pid;
       ap_generation_t generation;	/* generation of this child */
       int worker_threads;
  -    int acceptor_threads;
   #ifdef OPTIMIZE_TIMEOUTS
       time_t last_rtime;		/* time(0) of the last change */
       vtime_t last_vtime;		/* the last vtime the parent has seen */
  
  
  
  1.4       +15 -26    apache-2.0/mpm/src/modules/mpm/mpmt_pthread/Makefile.tmpl
  
  Index: Makefile.tmpl
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/mpmt_pthread/Makefile.tmpl,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -d -u -r1.3 -r1.4
  --- Makefile.tmpl	1999/07/07 15:11:05	1.3
  +++ Makefile.tmpl	1999/07/09 20:40:19	1.4
  @@ -2,9 +2,9 @@
   LIB=libmpmt_pthread.$(LIBEXT)
   
   OBJS=\
  -     mpmt_pthread.o acceptlock.o http_accept.o scoreboard.o
  +     mpmt_pthread.o acceptlock.o scoreboard.o
   OBJS_PIC=\
  -     mpmt_pthread.lo acceptlock.lo http_accept.lo scoreboard.lo
  +     mpmt_pthread.lo acceptlock.lo scoreboard.lo
   
   all: lib
   
  @@ -64,22 +64,11 @@
    $(INCDIR)/ap.h $(INCDIR)/apr.h \
    $(INCDIR)/util_uri.h $(INCDIR)/http_main.h \
    $(INCDIR)/http_log.h $(INCDIR)/http_config.h \
  - $(INCDIR)/http_protocol.h $(INCDIR)/http_request.h \
  - $(INCDIR)/http_conf_globals.h $(INCDIR)/http_core.h \
  - $(INCDIR)/http_vhost.h $(INCDIR)/util_script.h \
  - acceptlock.h $(INCDIR)/http_accept.h
  -http_accept.o: http_accept.c $(INCDIR)/httpd.h \
  - $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
  - $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \
  - $(OSDIR)/os-inline.c $(INCDIR)/ap_ctype.h \
  - $(INCDIR)/hsregex.h $(INCDIR)/alloc.h \
  - $(INCDIR)/buff.h $(INCDIR)/ap_iol.h \
  - $(INCDIR)/ap.h $(INCDIR)/apr.h \
  - $(INCDIR)/util_uri.h $(INCDIR)/http_log.h \
  - $(INCDIR)/http_main.h $(INCDIR)/http_conf_globals.h \
  - acceptlock.h $(INCDIR)/scoreboard.h  \
  - $(INCDIR)/http_config.h $(INCDIR)/ap_listen.h \
  - $(INCDIR)/http_accept.h mpmt_pthread.h
  + $(INCDIR)/ap_hooks.h $(INCDIR)/http_protocol.h \
  + $(INCDIR)/http_request.h $(INCDIR)/http_conf_globals.h \
  + $(INCDIR)/http_core.h $(INCDIR)/http_vhost.h \
  + $(INCDIR)/util_script.h acceptlock.h mpmt_pthread.h \
  + $(OSDIR)/unixd.h
   mpmt_pthread.o: mpmt_pthread.c $(INCDIR)/httpd.h \
    $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
    $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \
  @@ -89,11 +78,11 @@
    $(INCDIR)/ap.h $(INCDIR)/apr.h \
    $(INCDIR)/util_uri.h $(INCDIR)/http_main.h \
    $(INCDIR)/http_log.h $(INCDIR)/http_config.h \
  - $(INCDIR)/http_core.h $(INCDIR)/http_connection.h \
  - $(INCDIR)/ap_mpm.h $(OSDIR)/unixd.h \
  - $(OSDIR)/iol_socket.h $(INCDIR)/ap_listen.h \
  - $(INCDIR)/scoreboard.h  acceptlock.h \
  - $(INCDIR)/http_accept.h
  + $(INCDIR)/ap_hooks.h $(INCDIR)/http_core.h \
  + $(INCDIR)/http_connection.h $(INCDIR)/ap_mpm.h \
  + $(OSDIR)/unixd.h $(OSDIR)/iol_socket.h \
  + $(INCDIR)/ap_listen.h $(INCDIR)/scoreboard.h \
  + acceptlock.h ../../../lib/expat-lite/xmlparse.h
   scoreboard.o: scoreboard.c $(INCDIR)/httpd.h \
    $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
    $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \
  @@ -103,6 +92,6 @@
    $(INCDIR)/ap.h $(INCDIR)/apr.h \
    $(INCDIR)/util_uri.h $(INCDIR)/http_log.h \
    $(INCDIR)/http_main.h $(INCDIR)/http_core.h \
  - $(INCDIR)/http_config.h $(OSDIR)/unixd.h \
  - $(INCDIR)/http_conf_globals.h mpmt_pthread.h \
  - $(INCDIR)/scoreboard.h
  + $(INCDIR)/http_config.h $(INCDIR)/ap_hooks.h \
  + $(OSDIR)/unixd.h $(INCDIR)/http_conf_globals.h \
  + mpmt_pthread.h $(INCDIR)/scoreboard.h
  
  
  
  1.5       +2 -2      apache-2.0/mpm/src/modules/mpm/mpmt_pthread/acceptlock.c
  
  Index: acceptlock.c
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/mpmt_pthread/acceptlock.c,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -d -u -r1.4 -r1.5
  --- acceptlock.c	1999/07/09 20:30:15	1.4
  +++ acceptlock.c	1999/07/09 20:40:20	1.5
  @@ -67,8 +67,8 @@
   #include "util_script.h"	/* to force util_script.c linking */ 
   #include "util_uri.h" 
   #include "acceptlock.h"
  +#include "mpmt_pthread.h"	/* For clean_child_exit */
   #include "unixd.h"
  -#include "http_accept.h"
   #include <netinet/tcp.h> 
   #include <stdio.h> 
   
  @@ -78,7 +78,7 @@
   #include <sys/shm.h>
   #endif
    
  -#include <pthread.h> 
  +#include <pthread.h>
   
   /* TODO: all these calls have to be ap_ prefixed, right? */
   /* Number of cross-process locks we're managing */
  
  
  
  1.6       +150 -35   apache-2.0/mpm/src/modules/mpm/mpmt_pthread/mpmt_pthread.c
  
  Index: mpmt_pthread.c
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/mpmt_pthread/mpmt_pthread.c,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -d -u -r1.5 -r1.6
  --- mpmt_pthread.c	1999/07/09 20:30:15	1.5
  +++ mpmt_pthread.c	1999/07/09 20:40:22	1.6
  @@ -70,16 +70,15 @@
   #include "scoreboard.h" 
   #include "acceptlock.h"
   
  -#include "http_accept.h"
  +#include <poll.h>
   #include <netinet/tcp.h> 
  -#include <pthread.h> 
  +#include <pthread.h>
   
   /*
    * Actual definitions of config globals
    */
   
   int ap_threads_per_child=0;         /* Worker threads per child */
  -int ap_acceptors_per_child=0;       /* Accept threads per child */
   int ap_max_requests_per_child=0;
   static char *ap_pid_fname=NULL;
   static char *ap_scoreboard_fname=NULL;
  @@ -89,7 +88,25 @@
   static int ap_daemons_limit=0;
   static time_t ap_restart_time=0;
   API_VAR_EXPORT int ap_extended_status = 0;
  +static int workers_may_exit = 0;
  +static int requests_this_child;
  +static int num_listenfds = 0;
  +static struct pollfd *listenfds_child; /* The listenfds that each thread copies
  +                                          for itself */
  +
  +/* The structure used to pass unique initialization info to each thread */
  +typedef struct {
  +    int pid;
  +    int tid;
  +    int sd;
  +    pool *tpool; /* "pthread" would be confusing */
  +} proc_info;
   
  +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
  +#define SAFE_ACCEPT(stmt) do {if (ap_listeners->next != NULL) {stmt;}} while (0)
  +#else
  +#define SAFE_ACCEPT(stmt) do {stmt;} while (0)
  +#endif
   
   /*
    * The max child slot ever assigned, preserved across restarts.  Necessary
  @@ -100,7 +117,8 @@
   
   static char ap_coredump_dir[MAX_STRING_LEN];
   
  -int ap_pipe_of_death[2];
  +static int pipe_of_death[2];
  +static pthread_mutex_t pipe_of_death_mutex;
   
   /* *Non*-shared http_main globals... */
   
  @@ -841,8 +859,15 @@
       int thread_slot = ti->tid;
       pool *tpool = ti->tpool;
       struct sockaddr sa_client;
  -    int csd;
  +    int csd = -1;
       pool *ptrans;		/* Pool for per-transaction stuff */
  +    int sd = -1;
  +    int srv;
  +    int ret;
  +    char pipe_read_char;
  +    int curr_pollfd, last_pollfd = 0;
  +    size_t len = sizeof(struct sockaddr);
  +    struct pollfd *listenfds;
   
       free(ti);
   
  @@ -852,15 +877,91 @@
       worker_thread_count++;
       pthread_mutex_unlock(&worker_thread_count_mutex);
   
  -    while (1) {
  +    /* TODO: Switch to a system where threads reuse the results from earlier
  +       poll calls - manoj */
  +    /* set up each thread's individual pollfd array */
  +    listenfds = ap_palloc(tpool, sizeof(struct pollfd) * (num_listenfds + 1));
  +    memcpy(listenfds, listenfds_child, sizeof(struct pollfd) * (num_listenfds + 1));
  +    while (!workers_may_exit) {
  +        workers_may_exit |= (ap_max_requests_per_child != 0) && (requests_this_child
<= 0);
  +        if (workers_may_exit) break;
  +
           (void) ap_update_child_status(process_slot, thread_slot, SERVER_READY, 
  -				  (request_rec *) NULL);
  -        csd = get_connection(&sa_client);
  -	if (csd < 0) {
  +                                      (request_rec *) NULL);
  +        SAFE_ACCEPT(intra_mutex_on(0));
  +        if (workers_may_exit) {
  +            SAFE_ACCEPT(intra_mutex_off(0));
               break;
  -        } 
  +        }
  +        SAFE_ACCEPT(accept_mutex_on(0));
  +        while (!workers_may_exit) {
  +            srv = poll(listenfds, num_listenfds + 1, -1);
  +            if (srv < 0) {
  +                if (errno == EINTR) {
  +                    continue;
  +                }
  +
  +                /* poll() will only return errors in catastrophic
  +                 * circumstances. Let's try exiting gracefully, for now. */
  +                ap_log_error(APLOG_MARK, APLOG_ERR, (const server_rec *)
  +                             ap_get_server_conf(), "poll: (listen)");
  +                workers_may_exit = 1;
  +            }
  +
  +            if (workers_may_exit) break;
  +
  +            if (listenfds[0].revents & POLLIN) {
  +                /* A process got a signal on the shutdown pipe. Check if we're
  +                 * the lucky process to die. */
  +                pthread_mutex_lock(&pipe_of_death_mutex);
  +                if (!workers_may_exit) {
  +                    ret = read(listenfds[0].fd, &pipe_read_char, 1);
  +                    if (ret == -1 && errno == EAGAIN) {
  +                        /* It lost the lottery. It must continue to suffer
  +                         * through a life of servitude. */
  +                        pthread_mutex_unlock(&pipe_of_death_mutex);
  +                        continue;
  +                    }
  +                    else {
  +                        /* It won the lottery (or something else is very
  +                         * wrong). Embrace death with open arms. */
  +                        workers_may_exit = 1;
  +                        pthread_mutex_unlock(&pipe_of_death_mutex);
  +                        break;
  +                    }
  +                }
  +                pthread_mutex_unlock(&pipe_of_death_mutex);
  +            }
  +
  +            if (num_listenfds == 1) {
  +                sd = ap_listeners->fd;
  +                goto got_fd;
  +            }
  +            else {
  +                /* find a listener */
  +                curr_pollfd = last_pollfd;
  +                do {
  +                    curr_pollfd++;
  +                    if (curr_pollfd > num_listenfds) {
  +                        curr_pollfd = 1;
  +                    }
  +                    /* XXX: Should we check for POLLERR? */
  +                    if (listenfds[curr_pollfd].revents & POLLIN) {
  +                        last_pollfd = curr_pollfd;
  +                        sd = listenfds[curr_pollfd].fd;
  +                        goto got_fd;
  +                    }
  +                } while (curr_pollfd != last_pollfd);
  +            }
  +        }
  +    got_fd:
  +        SAFE_ACCEPT(accept_mutex_off(0));
  +        SAFE_ACCEPT(intra_mutex_off(0));
  +        if (workers_may_exit) break;
  +        csd = ap_accept(sd, &sa_client, &len);
           process_socket(ptrans, &sa_client, csd, process_slot, thread_slot);
  -	ap_clear_pool(ptrans);
  +        ap_clear_pool(ptrans);
  +        requests_this_child--;
       }
   
       ap_destroy_pool(tpool);
  @@ -888,6 +989,7 @@
       int i;
       int my_child_num = child_num_arg;
       proc_info *my_info = NULL;
  +    ap_listen_rec *lr;
   
       my_pid = getpid();
       pchild = ap_make_sub_pool(pconf);
  @@ -899,7 +1001,8 @@
   	clean_child_exit(APEXIT_CHILDFATAL);
       }
   
  -    accept_child_init(pchild, ap_threads_per_child);
  +    SAFE_ACCEPT(intra_mutex_init(pchild, 1));
  +    SAFE_ACCEPT(accept_mutex_child_init(pchild));
       ap_child_init_hook(pchild, server_conf);
   
       /*done with init critical section */
  @@ -911,10 +1014,24 @@
           ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, "pthread_sigmask");
       }
   
  +    requests_this_child = ap_max_requests_per_child;
  +    
  +    /* Set up the pollfd array */
  +    listenfds_child = ap_palloc(pchild, sizeof(struct pollfd) * (num_listenfds + 1));
  +    listenfds_child[0].fd = pipe_of_death[0];
  +    listenfds_child[0].events = POLLIN;
  +    listenfds_child[0].revents = 0;
  +    for (lr = ap_listeners, i = 1; i <= num_listenfds; lr = lr->next, ++i) {
  +        listenfds_child[i].fd = lr->fd;
  +        listenfds_child[i].events = POLLIN; /* should we add POLLPRI ?*/
  +        listenfds_child[i].revents = 0;
  +    }
  +
       /* Setup worker threads */
   
       worker_thread_count = 0;
       pthread_mutex_init(&worker_thread_count_mutex, NULL);
  +    pthread_mutex_init(&pipe_of_death_mutex, NULL);
       pthread_attr_init(&thread_attr);
       pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
       for (i=0; i < ap_threads_per_child; i++) {
  @@ -949,7 +1066,6 @@
       }
   
       pthread_attr_destroy(&thread_attr);
  -    start_accepting_connections(my_child_num);
   
       /* This thread will be the one responsible for handling signals */
       sigemptyset(&sig_mask);
  @@ -972,13 +1088,6 @@
   {
       int pid;
   
  -    if (ap_acceptors_per_child + ap_threads_per_child  > HARD_THREAD_LIMIT) {
  -        ap_log_error(APLOG_MARK, APLOG_ERR, s,
  -		     "Worker threads plus acceptor threads is greater than HARD_THREAD_LIMIT, please
correct");
  -	exit(-1);
  -    }
  -
  -
       if (slot + 1 > max_daemons_limit) {
   	max_daemons_limit = slot + 1;
       }
  @@ -1128,8 +1237,8 @@
       if (idle_count_ceil > ap_daemons_max_free) {
           /* Kill off one child */
           char char_of_death = '!';
  -        if (write(ap_pipe_of_death[1], &char_of_death, 1) == -1) {
  -            ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "write ap_pipe_of_death");
  +        if (write(pipe_of_death[1], &char_of_death, 1) == -1) {
  +            ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "write pipe_of_death");
           }
           idle_spawn_rate = 1;
       }
  @@ -1190,7 +1299,7 @@
           if (pid >= 0) {
               child_slot = find_child_by_pid(pid);
               if (child_slot >= 0) {
  -                for (i = 0; i < ap_threads_per_child + ap_acceptors_per_child; i++)
  +                for (i = 0; i < ap_threads_per_child; i++)
                       ap_update_child_status(child_slot, i, SERVER_DEAD, (request_rec *)
NULL);
                   
   		if (remaining_children_to_start
  @@ -1244,26 +1353,25 @@
   int ap_mpm_run(pool *_pconf, pool *plog, server_rec *s)
   {
       int remaining_children_to_start;
  -    int listener_count;
   
       pconf = _pconf;
       server_conf = s;
  -    if (pipe(ap_pipe_of_death) == -1) {
  +    if (pipe(pipe_of_death) == -1) {
           ap_log_error(APLOG_MARK, APLOG_ERR,
                        (const server_rec*) server_conf,
                        "pipe: (pipe_of_death)");
           exit(1);
       }
  -    ap_note_cleanups_for_fd(pconf, ap_pipe_of_death[0]);
  -    ap_note_cleanups_for_fd(pconf, ap_pipe_of_death[1]);
  -    if (fcntl(ap_pipe_of_death[0], F_SETFD, O_NONBLOCK) == -1) {
  +    ap_note_cleanups_for_fd(pconf, pipe_of_death[0]);
  +    ap_note_cleanups_for_fd(pconf, pipe_of_death[1]);
  +    if (fcntl(pipe_of_death[0], F_SETFD, O_NONBLOCK) == -1) {
           ap_log_error(APLOG_MARK, APLOG_ERR,
                        (const server_rec*) server_conf,
                        "fcntl: O_NONBLOCKing (pipe_of_death)");
           exit(1);
       }
       server_conf = s;
  -    if ((listener_count = setup_listeners(pconf, server_conf)) < 1) {
  +    if ((num_listenfds = setup_listeners(pconf, server_conf)) < 1) {
           /* XXX: hey, what's the right way for the mpm to indicate a fatal error? */
           ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ALERT, s,
               "no listening sockets available, shutting down");
  @@ -1272,7 +1380,7 @@
       ap_clear_pool(plog);
       ap_open_logs(server_conf, plog);
       ap_log_pid(pconf, ap_pid_fname);
  -    accept_parent_init(pconf, listener_count);
  +    SAFE_ACCEPT(accept_mutex_init(pconf, 1));
       if (!is_graceful) {
   	reinit_scoreboard(pconf);
       }
  @@ -1365,8 +1473,8 @@
   
   	/* kill off the idle ones */
           for (i = 0; i < ap_daemons_limit; ++i) {
  -            if (write(ap_pipe_of_death[1], &char_of_death, 1) == -1) {
  -                ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "write ap_pipe_of_death");
  +            if (write(pipe_of_death[1], &char_of_death, 1) == -1) {
  +                ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "write pipe_of_death");
               }
           }
   
  @@ -1375,8 +1483,7 @@
            */
   	
   	for (i = 0; i < ap_daemons_limit; ++i) {
  -  	    for (j = 0; j < ap_threads_per_child + ap_acceptors_per_child; 
  -		 j++) { 
  +  	    for (j = 0; j < ap_threads_per_child; j++) { 
   	        if (ap_scoreboard_image->servers[i][j].status != SERVER_DEAD) {
   		    ap_scoreboard_image->servers[i][j].status = SERVER_GRACEFUL;
   		}
  @@ -1548,7 +1655,15 @@
       }
   
       ap_threads_per_child = atoi(arg);
  -    if (ap_threads_per_child < 1) {
  +    if (ap_threads_per_child > HARD_THREAD_LIMIT) {
  +        fprintf(stderr, "WARNING: ThreadsPerChild of %d exceeds compile time"
  +                "limit of %d threads,\n", ap_threads_per_child,
  +                HARD_THREAD_LIMIT);
  +        fprintf(stderr, " lowering ThreadsPerChild to %d. To increase, please"
  +                "see the\n", HARD_THREAD_LIMIT);
  +        fprintf(stderr, " HARD_THREAD_LIMIT define in src/include/httpd.h.\n");
  +    }
  +    else if (ap_threads_per_child < 1) {
   	fprintf(stderr, "WARNING: Require ThreadsPerChild > 0, setting to 1\n");
   	ap_threads_per_child = 1;
       }
  
  
  
  1.2       +1 -1      apache-2.0/mpm/src/modules/mpm/mpmt_pthread/mpmt_pthread.h
  
  Index: mpmt_pthread.h
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/mpmt_pthread/mpmt_pthread.h,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -d -u -r1.1 -r1.2
  --- mpmt_pthread.h	1999/06/27 03:45:15	1.1
  +++ mpmt_pthread.h	1999/07/09 20:40:23	1.2
  @@ -56,8 +56,8 @@
    */ 
   
   extern int ap_threads_per_child;
  -extern int ap_acceptors_per_child;
   extern int ap_max_requests_per_child;
   extern int ap_pipe_of_death[2];
   extern void clean_child_exit(int);
   extern int ap_extended_status;
  +extern void clean_child_exit(int);
  
  
  
  1.3       +0 -1      apache-2.0/mpm/src/modules/mpm/mpmt_pthread/scoreboard.c
  
  Index: scoreboard.c
  ===================================================================
  RCS file: /home/cvs/apache-2.0/mpm/src/modules/mpm/mpmt_pthread/scoreboard.c,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -d -u -r1.2 -r1.3
  --- scoreboard.c	1999/06/27 03:45:15	1.2
  +++ scoreboard.c	1999/07/09 20:40:23	1.3
  @@ -590,7 +590,6 @@
   	&& old_status == SERVER_STARTING) {
           ss->tid = pthread_self();
   	ps->worker_threads = ap_threads_per_child;
  -	ps->acceptor_threads = ap_acceptors_per_child;
       }
   
       if (ap_extended_status) {
  
  
  

Mime
View raw message