|
Home > Archive > Unix Programming > January 2004 > Help with select()
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Help with select()
|
|
| Adam Bozanich 2004-01-23, 5:09 pm |
| Hi all. I am experimenting with select(). I have a little test program
<see below> that I have some questions about.
I can't figure out how you are able to determine if a fd has been closed.
Is this what the 'exceptfds' is used for? What conditions cause
bits in this field to be turned on?
My program works if there is not too much output. It will exit the loop
if the buffer is full, but it hangs on select() for commands like 'find /'
or 'locate a'. Can anybody see why?
Thanks,
Adam Bozanich
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<errno.h>
#define MAX(a,b) ( a > b ) ? a : b
#define READFD 0
#define WRITEFD 1
#define OUT_NUM 0
#define ERR_NUM 1
char** ex_program(char* line);
int main() {
char line[ BUFSIZ ];
char **returned;
for(;;) {
printf("> ");
fgets(line, BUFSIZ, stdin);
line[strlen(line) - 1] = '\0';
returned = ex_program(line);
if(returned == NULL)
{
fprintf(stderr,"something bad happened\n");
exit(EXIT_FAILURE);
}
printf("stdout:\n%s\nstderr:\n%s\n",returned[ OUT_NUM ]
,returned[ ERR_NUM ]);
free( returned [ OUT_NUM ] );
free( returned [ ERR_NUM ] );
free( returned );
}
exit(EXIT_SUCCESS);
}
char** ex_program(char* line)
{
/* one set of pipes for reading stdout, one set for stderr */
int stdout_fd[2];
int stderr_fd[2];
char **output;
/* output[ OUT_NUM ] -> stdout
* output[ ERR_NUM ] -> stderr
*/
int nread = 0;
int stdout_read = 0; /* total bytes read from child's stdout */
int stderr_read = 0; /* total bytes read from child's stderr */
int maxfd; /* highest fd value in fd_set + 1 */
int nready; /* number of descriptors ready */
int i;
int ret;
pid_t pid;
/* use select() to read both pipes */
fd_set readset;
fd_set fullset;
/* get some memory */
if((output = malloc( 2 * sizeof(char*))) == NULL) {
perror("malloc()");
return(NULL);
}
if((output[ OUT_NUM ] = malloc( BUFSIZ )) == NULL) {
perror("malloc()");
free(output);
return(NULL);
}
if((output[ ERR_NUM ] = malloc( BUFSIZ )) == NULL) {
perror("malloc()");
free(output[ OUT_NUM ]);
free(output);
return(NULL);
}
/* create pipes */
if( ( pipe(stderr_fd)<0) || (pipe(stdout_fd)<0)) {
free(output [ OUT_NUM ]);
free(output [ ERR_NUM ]);
free(output);
return(NULL);
}
FD_ZERO(&fullset);
FD_ZERO(&readset);
FD_SET( stdout_fd[ READFD ] , &fullset);
FD_SET( stderr_fd[ READFD ] , &fullset);
if((pid = fork() == 0)) { /* child */
close(stdout_fd[ READFD ]);
close(stderr_fd[ READFD ]);
if(dup2(stdout_fd[ WRITEFD ], 1)<0) /* stdout */
exit(EXIT_FAILURE);
if(dup2(stderr_fd[ WRITEFD ],2)<0) /* stderr */
exit(EXIT_FAILURE);
exit(system(line)); /* probably want to cook your own */
}
close( stdout_fd[ WRITEFD ] );
close( stderr_fd[ WRITEFD ] );
maxfd = MAX(stdout_fd[ READFD ],stderr_fd[ READFD ]) + 1;
readset = fullset;
/* loop until there are no more fd's in fullset */
while((nready = select(maxfd,&readset,NULL,NULL,NULL))>0) {
printf("here\n");
printf("nready = %d\nstdout_read= %d\nstderr_read =%d\n",
nready, stdout_read, stderr_read);
if(nready < 0) {
perror("select()");
free(output [ OUT_NUM ]);
free(output [ ERR_NUM ]);
free(output);
return(NULL);
}
for(i=0;i<nready;i++) {
/* check if child process wrote to stdout */
if(FD_ISSET(stdout_fd[0],&readset)) {
if( stdout_read < BUFSIZ - 1) {
nread = read( stdout_fd[ READFD ]
, output [ OUT_NUM ] + stdout_read
, BUFSIZ - stdout_read );
if( nread < 0 ) {
perror("read()");
FD_CLR( stdout_fd[ READFD ] , &fullset );
maxfd = stderr_fd[ READFD ] + 1;
continue;
}
else if( nread == 0 ) {
FD_CLR( stdout_fd[ READFD ] , &fullset );
maxfd = stderr_fd[ READFD ] + 1;
}
stdout_read += nread;
}
}
/* check if child process wrote to stderr */
if(FD_ISSET(stderr_fd[0],&readset)) {
if( stderr_read < BUFSIZ - 1) {
nread = read( stderr_fd[ READFD ]
, output [ ERR_NUM ] + stderr_read
, BUFSIZ - stderr_read );
if( nread < 0 ) {
perror("read()");
FD_CLR( stderr_fd[ READFD ] , &fullset );
maxfd = stdout_fd[ READFD ] + 1;
continue;
}
else if( nread == 0 ) {
FD_CLR( stderr_fd[ READFD ] , &fullset );
maxfd = stdout_fd[ READFD ] + 1;
}
stderr_read += nread;
}
}
}
if( ! ((stderr_read < BUFSIZ) && (stdout_read < BUFSIZ)) )
break;
if( !( FD_ISSET(stdout_fd[ READFD ],&fullset)
|| FD_ISSET(stderr_fd[ READFD ],&fullset)))
break;
readset = fullset;
}
close(stdout_fd[ READFD ]);
close(stderr_fd[ READFD ]);
if(wait(&ret)<0)
{
if( errno != ECHILD )
{
perror("wait()");
free(output [ OUT_NUM ]);
free(output [ ERR_NUM ]);
free(output);
return(NULL);
}
}
output [ OUT_NUM ][ stdout_read ] = '\0';
output [ ERR_NUM ][ stderr_read ] = '\0';
return(output);
}
| |
| Barry Margolin 2004-01-23, 5:09 pm |
| In article
<Pine.HPX.4.44.0312172114320.3974-100000@hills.ccsf.cc.ca.us>,
Adam Bozanich <abozan01@ccsf.edu> wrote:
quote:
> Hi all. I am experimenting with select(). I have a little test program
> <see below> that I have some questions about.
>
> I can't figure out how you are able to determine if a fd has been closed.
> Is this what the 'exceptfds' is used for? What conditions cause
> bits in this field to be turned on?
It depends on the type of device. In the case of TCP connections, only
out-of-band data causes an exception.
If the socket has been closed, select() will report it as ready to read.
Then when you read from it you'll get 0 bytes, indicating EOF.
quote:
> My rec.arts.sf.movies program works if there is not too much output. It will exit the loop
> if the buffer is full, but it hangs on select() for commands like 'find /'
> or 'locate a'. Can anybody see why?
These programs probably take a long time to run. Your program will
unhang when they're finished.
They're probably using stdio to write their output, and it buffers the
output when it's not going to a terminal. Since you've got their output
redirected to a pipe, this buffering takes place. So there won't be
anything in the pipe for select() to notice until the buffer has filled
up or the pipe has been closed.
Other comments on the program are inline below.
quote:
> Thanks,
> Adam Bozanich
>
> #include<stdio.h>
> #include<stdlib.h>
> #include<sys/types.h>
> #include<errno.h>
>
> #define MAX(a,b) ( a > b ) ? a : b
>
> #define READFD 0
> #define WRITEFD 1
>
> #define OUT_NUM 0
> #define ERR_NUM 1
>
> char** ex_program(char* line);
>
> int main() {
>
> char line[ BUFSIZ ];
> char **returned;
>
> for(;;) {
> printf("> ");
>
> fgets(line, BUFSIZ, stdin);
> line[strlen(line) - 1] = '\0';
>
> returned = ex_program(line);
>
> if(returned == NULL)
> {
> fprintf(stderr,"something bad happened\n");
> exit(EXIT_FAILURE);
> }
>
> printf("stdout:\n%s\nstderr:\n%s\n",returned[ OUT_NUM ]
> ,returned[ ERR_NUM ]);
>
> free( returned [ OUT_NUM ] );
> free( returned [ ERR_NUM ] );
> free( returned );
> }
>
> exit(EXIT_SUCCESS);
> }
>
> char** ex_program(char* line)
> {
> /* one set of pipes for reading stdout, one set for stderr */
> int stdout_fd[2];
> int stderr_fd[2];
>
> char **output;
> /* output[ OUT_NUM ] -> stdout
> * output[ ERR_NUM ] -> stderr
> */
>
> int nread = 0;
>
> int stdout_read = 0; /* total bytes read from child's stdout */
> int stderr_read = 0; /* total bytes read from child's stderr */
>
> int maxfd; /* highest fd value in fd_set + 1 */
> int nready; /* number of descriptors ready */
>
> int i;
> int ret;
>
> pid_t pid;
>
> /* use select() to read both pipes */
> fd_set readset;
> fd_set fullset;
>
>
> /* get some memory */
> if((output = malloc( 2 * sizeof(char*))) == NULL) {
> perror("malloc()");
> return(NULL);
> }
>
> if((output[ OUT_NUM ] = malloc( BUFSIZ )) == NULL) {
> perror("malloc()");
> free(output);
> return(NULL);
> }
>
> if((output[ ERR_NUM ] = malloc( BUFSIZ )) == NULL) {
> perror("malloc()");
> free(output[ OUT_NUM ]);
> free(output);
> return(NULL);
> }
>
> /* create pipes */
> if( ( pipe(stderr_fd)<0) || (pipe(stdout_fd)<0)) {
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
>
> FD_ZERO(&fullset);
> FD_ZERO(&readset);
Since you always do "readset = fullset" before calling select(), you
don't need to zero readset.
quote:
> FD_SET( stdout_fd[ READFD ] , &fullset);
> FD_SET( stderr_fd[ READFD ] , &fullset);
>
> if((pid = fork() == 0)) { /* child */
> close(stdout_fd[ READFD ]);
> close(stderr_fd[ READFD ]);
>
> if(dup2(stdout_fd[ WRITEFD ], 1)<0) /* stdout */
> exit(EXIT_FAILURE);
> if(dup2(stderr_fd[ WRITEFD ],2)<0) /* stderr */
> exit(EXIT_FAILURE);
You should use the STDOUT_FILENO and STDERR_FILENO macros rather than
hard-coding 1 and 2.
After duping the pipe fd's, you need to close them. Otherwise, when the
program closes its stdout, the pipe won't really be closed (because
there's still another open fd referring to it).
quote:
> exit(system(line)); /* probably want to cook your own */
> }
>
> close( stdout_fd[ WRITEFD ] );
> close( stderr_fd[ WRITEFD ] );
>
> maxfd = MAX(stdout_fd[ READFD ],stderr_fd[ READFD ]) + 1;
>
> readset = fullset;
>
> /* loop until there are no more fd's in fullset */
> while((nready = select(maxfd,&readset,NULL,NULL,NULL))>0) {
>
> printf("here\n");
>
>
> printf("nready = %d\nstdout_read= %d\nstderr_read =%d\n",
> nready, stdout_read, stderr_read);
>
> if(nready < 0) {
> perror("select()");
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
>
> for(i=0;i<nready;i++) {
>
> /* check if child process wrote to stdout */
> if(FD_ISSET(stdout_fd[0],&readset)) {
> if( stdout_read < BUFSIZ - 1) {
>
> nread = read( stdout_fd[ READFD ]
> , output [ OUT_NUM ] + stdout_read
> , BUFSIZ - stdout_read );
>
> if( nread < 0 ) {
> perror("read()");
> FD_CLR( stdout_fd[ READFD ] , &fullset );
> maxfd = stderr_fd[ READFD ] + 1;
> continue;
> }
> else if( nread == 0 ) {
> FD_CLR( stdout_fd[ READFD ] , &fullset );
> maxfd = stderr_fd[ READFD ] + 1;
> }
> stdout_read += nread;
> }
> }
>
> /* check if child process wrote to stderr */
> if(FD_ISSET(stderr_fd[0],&readset)) {
> if( stderr_read < BUFSIZ - 1) {
>
> nread = read( stderr_fd[ READFD ]
> , output [ ERR_NUM ] + stderr_read
> , BUFSIZ - stderr_read );
>
> if( nread < 0 ) {
> perror("read()");
> FD_CLR( stderr_fd[ READFD ] , &fullset );
> maxfd = stdout_fd[ READFD ] + 1;
> continue;
> }
> else if( nread == 0 ) {
> FD_CLR( stderr_fd[ READFD ] , &fullset );
> maxfd = stdout_fd[ READFD ] + 1;
> }
> stderr_read += nread;
> }
> }
> }
You're never checking for EOF having been read from *both* stdout and
stderr.
quote:
> if( ! ((stderr_read < BUFSIZ) && (stdout_read < BUFSIZ)) )
> break;
>
> if( !( FD_ISSET(stdout_fd[ READFD ],&fullset)
> || FD_ISSET(stderr_fd[ READFD ],&fullset)))
> break;
>
> readset = fullset;
> }
>
> close(stdout_fd[ READFD ]);
> close(stderr_fd[ READFD ]);
>
> if(wait(&ret)<0)
> {
> if( errno != ECHILD )
> {
> perror("wait()");
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
> }
>
> output [ OUT_NUM ][ stdout_read ] = '\0';
> output [ ERR_NUM ][ stderr_read ] = '\0';
>
> return(output);
> }
>
>
--
Barry Margolin, barmar@alum.mit.edu
Arlington, MA
| |
| Barry Margolin 2004-01-23, 5:09 pm |
| In article
<Pine.HPX.4.44.0312172114320.3974-100000@hills.ccsf.cc.ca.us>,
Adam Bozanich <abozan01@ccsf.edu> wrote:
quote:
> Hi all. I am experimenting with select(). I have a little test program
> <see below> that I have some questions about.
>
> I can't figure out how you are able to determine if a fd has been closed.
> Is this what the 'exceptfds' is used for? What conditions cause
> bits in this field to be turned on?
It depends on the type of device. In the case of TCP connections, only
out-of-band data causes an exception.
If the socket has been closed, select() will report it as ready to read.
Then when you read from it you'll get 0 bytes, indicating EOF.
quote:
> My rec.arts.sf.movies program works if there is not too much output. It will exit the loop
> if the buffer is full, but it hangs on select() for commands like 'find /'
> or 'locate a'. Can anybody see why?
These programs probably take a long time to run. Your program will
unhang when they're finished.
They're probably using stdio to write their output, and it buffers the
output when it's not going to a terminal. Since you've got their output
redirected to a pipe, this buffering takes place. So there won't be
anything in the pipe for select() to notice until the buffer has filled
up or the pipe has been closed.
Other comments on the program are inline below.
quote:
> Thanks,
> Adam Bozanich
>
> #include<stdio.h>
> #include<stdlib.h>
> #include<sys/types.h>
> #include<errno.h>
>
> #define MAX(a,b) ( a > b ) ? a : b
>
> #define READFD 0
> #define WRITEFD 1
>
> #define OUT_NUM 0
> #define ERR_NUM 1
>
> char** ex_program(char* line);
>
> int main() {
>
> char line[ BUFSIZ ];
> char **returned;
>
> for(;;) {
> printf("> ");
>
> fgets(line, BUFSIZ, stdin);
> line[strlen(line) - 1] = '\0';
>
> returned = ex_program(line);
>
> if(returned == NULL)
> {
> fprintf(stderr,"something bad happened\n");
> exit(EXIT_FAILURE);
> }
>
> printf("stdout:\n%s\nstderr:\n%s\n",returned[ OUT_NUM ]
> ,returned[ ERR_NUM ]);
>
> free( returned [ OUT_NUM ] );
> free( returned [ ERR_NUM ] );
> free( returned );
> }
>
> exit(EXIT_SUCCESS);
> }
>
> char** ex_program(char* line)
> {
> /* one set of pipes for reading stdout, one set for stderr */
> int stdout_fd[2];
> int stderr_fd[2];
>
> char **output;
> /* output[ OUT_NUM ] -> stdout
> * output[ ERR_NUM ] -> stderr
> */
>
> int nread = 0;
>
> int stdout_read = 0; /* total bytes read from child's stdout */
> int stderr_read = 0; /* total bytes read from child's stderr */
>
> int maxfd; /* highest fd value in fd_set + 1 */
> int nready; /* number of descriptors ready */
>
> int i;
> int ret;
>
> pid_t pid;
>
> /* use select() to read both pipes */
> fd_set readset;
> fd_set fullset;
>
>
> /* get some memory */
> if((output = malloc( 2 * sizeof(char*))) == NULL) {
> perror("malloc()");
> return(NULL);
> }
>
> if((output[ OUT_NUM ] = malloc( BUFSIZ )) == NULL) {
> perror("malloc()");
> free(output);
> return(NULL);
> }
>
> if((output[ ERR_NUM ] = malloc( BUFSIZ )) == NULL) {
> perror("malloc()");
> free(output[ OUT_NUM ]);
> free(output);
> return(NULL);
> }
>
> /* create pipes */
> if( ( pipe(stderr_fd)<0) || (pipe(stdout_fd)<0)) {
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
>
> FD_ZERO(&fullset);
> FD_ZERO(&readset);
Since you always do "readset = fullset" before calling select(), you
don't need to zero readset.
quote:
> FD_SET( stdout_fd[ READFD ] , &fullset);
> FD_SET( stderr_fd[ READFD ] , &fullset);
>
> if((pid = fork() == 0)) { /* child */
> close(stdout_fd[ READFD ]);
> close(stderr_fd[ READFD ]);
>
> if(dup2(stdout_fd[ WRITEFD ], 1)<0) /* stdout */
> exit(EXIT_FAILURE);
> if(dup2(stderr_fd[ WRITEFD ],2)<0) /* stderr */
> exit(EXIT_FAILURE);
You should use the STDOUT_FILENO and STDERR_FILENO macros rather than
hard-coding 1 and 2.
After duping the pipe fd's, you need to close them. Otherwise, when the
program closes its stdout, the pipe won't really be closed (because
there's still another open fd referring to it).
quote:
> exit(system(line)); /* probably want to cook your own */
> }
>
> close( stdout_fd[ WRITEFD ] );
> close( stderr_fd[ WRITEFD ] );
>
> maxfd = MAX(stdout_fd[ READFD ],stderr_fd[ READFD ]) + 1;
>
> readset = fullset;
>
> /* loop until there are no more fd's in fullset */
> while((nready = select(maxfd,&readset,NULL,NULL,NULL))>0) {
>
> printf("here\n");
>
>
> printf("nready = %d\nstdout_read= %d\nstderr_read =%d\n",
> nready, stdout_read, stderr_read);
>
> if(nready < 0) {
> perror("select()");
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
>
> for(i=0;i<nready;i++) {
>
> /* check if child process wrote to stdout */
> if(FD_ISSET(stdout_fd[0],&readset)) {
> if( stdout_read < BUFSIZ - 1) {
>
> nread = read( stdout_fd[ READFD ]
> , output [ OUT_NUM ] + stdout_read
> , BUFSIZ - stdout_read );
>
> if( nread < 0 ) {
> perror("read()");
> FD_CLR( stdout_fd[ READFD ] , &fullset );
> maxfd = stderr_fd[ READFD ] + 1;
> continue;
> }
> else if( nread == 0 ) {
> FD_CLR( stdout_fd[ READFD ] , &fullset );
> maxfd = stderr_fd[ READFD ] + 1;
> }
> stdout_read += nread;
> }
> }
>
> /* check if child process wrote to stderr */
> if(FD_ISSET(stderr_fd[0],&readset)) {
> if( stderr_read < BUFSIZ - 1) {
>
> nread = read( stderr_fd[ READFD ]
> , output [ ERR_NUM ] + stderr_read
> , BUFSIZ - stderr_read );
>
> if( nread < 0 ) {
> perror("read()");
> FD_CLR( stderr_fd[ READFD ] , &fullset );
> maxfd = stdout_fd[ READFD ] + 1;
> continue;
> }
> else if( nread == 0 ) {
> FD_CLR( stderr_fd[ READFD ] , &fullset );
> maxfd = stdout_fd[ READFD ] + 1;
> }
> stderr_read += nread;
> }
> }
> }
You're never checking for EOF having been read from *both* stdout and
stderr.
quote:
> if( ! ((stderr_read < BUFSIZ) && (stdout_read < BUFSIZ)) )
> break;
>
> if( !( FD_ISSET(stdout_fd[ READFD ],&fullset)
> || FD_ISSET(stderr_fd[ READFD ],&fullset)))
> break;
>
> readset = fullset;
> }
>
> close(stdout_fd[ READFD ]);
> close(stderr_fd[ READFD ]);
>
> if(wait(&ret)<0)
> {
> if( errno != ECHILD )
> {
> perror("wait()");
> free(output [ OUT_NUM ]);
> free(output [ ERR_NUM ]);
> free(output);
> return(NULL);
> }
> }
>
> output [ OUT_NUM ][ stdout_read ] = '\0';
> output [ ERR_NUM ][ stderr_read ] = '\0';
>
> return(output);
> }
>
>
--
Barry Margolin, barmar@alum.mit.edu
Arlington, MA
| |
| Adam Bozanich 2004-01-23, 5:10 pm |
|
On Fri, 19 Dec 2003, Barry Margolin wrote:
quote:
> In article
> <Pine.HPX.4.44.0312172114320.3974-100000@hills.ccsf.cc.ca.us>,
> Adam Bozanich <abozan01@ccsf.edu> wrote:
>
>
> It depends on the type of device. In the case of TCP connections, only
> out-of-band data causes an exception.
>
> If the socket has been closed, select() will report it as ready to read.
> Then when you read from it you'll get 0 bytes, indicating EOF.
Perfect!
quote:
>
>
> These programs probably take a long time to run. Your program will
> unhang when they're finished.
>
> They're probably using stdio to write their output, and it buffers the
> output when it's not going to a terminal. Since you've got their output
> redirected to a pipe, this buffering takes place. So there won't be
> anything in the pipe for select() to notice until the buffer has filled
> up or the pipe has been closed.
Is there a way to this from the parent process?
Thanks for all of your comments.
-Adam Bozanich
| |
| Adam Bozanich 2004-01-23, 5:10 pm |
|
On Fri, 19 Dec 2003, Barry Margolin wrote:
quote:
> In article
> <Pine.HPX.4.44.0312172114320.3974-100000@hills.ccsf.cc.ca.us>,
> Adam Bozanich <abozan01@ccsf.edu> wrote:
>
>
> It depends on the type of device. In the case of TCP connections, only
> out-of-band data causes an exception.
>
> If the socket has been closed, select() will report it as ready to read.
> Then when you read from it you'll get 0 bytes, indicating EOF.
Perfect!
quote:
>
>
> These programs probably take a long time to run. Your program will
> unhang when they're finished.
>
> They're probably using stdio to write their output, and it buffers the
> output when it's not going to a terminal. Since you've got their output
> redirected to a pipe, this buffering takes place. So there won't be
> anything in the pipe for select() to notice until the buffer has filled
> up or the pipe has been closed.
Is there a way to this from the parent process?
Thanks for all of your comments.
-Adam Bozanich
| |
| Barry Margolin 2004-01-23, 5:10 pm |
| In article
<Pine.HPX.4.44.0312201626460.17249-100000@hills.ccsf.cc.ca.us>,
Adam Bozanich <abozan01@ccsf.edu> wrote:quote:
>
> Is there a way to this from the parent process?
Use a pty instead of a pipe, so it thinks it's writing to a terminal.
--
Barry Margolin, barmar@alum.mit.edu
Arlington, MA
| |
| Barry Margolin 2004-01-23, 5:10 pm |
| In article
<Pine.HPX.4.44.0312201626460.17249-100000@hills.ccsf.cc.ca.us>,
Adam Bozanich <abozan01@ccsf.edu> wrote:quote:
>
> Is there a way to this from the parent process?
Use a pty instead of a pipe, so it thinks it's writing to a terminal.
--
Barry Margolin, barmar@alum.mit.edu
Arlington, MA
|
|
|
|
|