home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / unix / volume27 / team / part01 < prev    next >
Encoding:
Text File  |  1994-01-13  |  33.0 KB  |  1,334 lines

  1. Newsgroups: comp.sources.unix
  2. From: pcg@aber.ac.uk (Piercarlo Grandi)
  3. Subject: v27i195: team - portable multi-buffered tape streaming utility, Part01/01
  4. Message-id: <1.758496249.28141@gw.home.vix.com>
  5. Sender: unix-sources-moderator@gw.home.vix.com
  6. Approved: vixie@gw.home.vix.com
  7.  
  8. Submitted-By: pcg@aber.ac.uk (Piercarlo Grandi)
  9. Posting-Number: Volume 27, Issue 195
  10. Archive-Name: team/part01
  11.  
  12. There exist a few filters that help tapes streams by buffering IO and
  13. allowing reads to overlaps with writes under Unix. Most of these filters
  14. rely on relatively unportable features, for example SYSV like shared
  15. memory.
  16.  
  17. team is a filter that runs essentially unchanged on any Unix version, as
  18. it relies only on features present in V7. A number of team processes
  19. (team members) share a common input fd and a common output fd, and they
  20. take turns at reading from the former and writing to the latter; they
  21. synchronize by using a ring of pipes between them, where a "read-enable"
  22. and a "write-enable" token circulate.
  23.  
  24. team is not just very portable, but also portable and efficient. It also
  25. has some bells & whistles, like command line options to specify the
  26. number of processes in a team, the block size for IO, and the volume
  27. size of the input or output media. It also optionally reports its
  28. progress.
  29.  
  30. Previous versions of team have been circulating (e.g. via alt.sources)
  31. for several years; I have not found a bug for a long time, even if
  32. surely they will exist.
  33.  
  34. The team source is GPL'ed, and it comes with no warranty.
  35.  
  36.   Note: this program was developed entirely by the author in his own
  37.   time, using his own resources, on his machine, in the context of 
  38.   his own research activities. In no way has the University of Wales,
  39.   Aberystwyth contributed aided or abetted to this work, for which
  40.   they bear no responsibility whatsoever. I am grateful to UWA for the
  41.   ability to use their systems (as a paying customer) to post this work.
  42.  
  43.     pcg@aber.ac.uk (Piercarlo Grandi)
  44.  
  45. #! /bin/sh
  46. # This is a shell archive.  Remove anything before this line, then unpack
  47. # it by saving it into a file and typing "sh file".  To overwrite existing
  48. # files, type "sh file -c".  You can also feed this as standard input via
  49. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  50. # will see the following message at the end:
  51. #        "End of shell archive."
  52. # Contents:  Makefile team.1 team.c
  53. # Wrapped by pcg@decb.aber.ac.uk on Thu Jan 13 19:00:25 1994
  54. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  55. if test -f 'Makefile' -a "${1}" != "-c" ; then 
  56.   echo shar: Will not clobber existing file \"'Makefile'\"
  57. else
  58. echo shar: Extracting \"'Makefile'\" \(314 characters\)
  59. sed "s/^X//" >'Makefile' <<'END_OF_FILE'
  60. XCFLAGS        =-O
  61. XLDFLAGS        =-s
  62. X
  63. XINSTX        =install -m 0755 -s
  64. XINSTD        =install -m 0644
  65. X
  66. XDEST=        
  67. X
  68. XMI        =$(DEST)/usr/
  69. XMD        =$(DEST)/usr/
  70. X
  71. XM1X        =1
  72. X
  73. XBIND        =$(MD)bin/
  74. XMANI        =$(MI)man
  75. X
  76. XMANI1        =$(MANI)$(M1X)/
  77. X
  78. Xall:        team
  79. X
  80. Xclean::
  81. X    rm -f team team.o
  82. X
  83. X$(BIND)team:        team;            $(INSTX) $? $@
  84. X$(MANI1)team.$(M1X):    team.1;            $(INSTD) $? $@
  85. END_OF_FILE
  86. if test 314 -ne `wc -c <'Makefile'`; then
  87.     echo shar: \"'Makefile'\" unpacked with wrong size!
  88. fi
  89. # end of 'Makefile'
  90. fi
  91. if test -f 'team.1' -a "${1}" != "-c" ; then 
  92.   echo shar: Will not clobber existing file \"'team.1'\"
  93. else
  94. echo shar: Extracting \"'team.1'\" \(5406 characters\)
  95. sed "s/^X//" >'team.1' <<'END_OF_FILE'
  96. X'\" Copyright 1987,1989 Piercarlo Grandi. All rights reserved.
  97. X.TH TEAM 1 (pg)
  98. X.SH NAME
  99. Xteam \- parallel "pipe", allows asynchronous io
  100. X.SH SYNOPSIS
  101. X.B team
  102. X.RB [ -r ]
  103. X.RB [ -v ]
  104. X.RB [ -i
  105. X.I volsize
  106. X.RB [ b | k | m ]]
  107. X.RB [ -o
  108. X.I volsize
  109. X.RB [ b | k | m ]]
  110. X.RI [ blocksize
  111. X.R [
  112. X.RB [ b | k | m ]
  113. X.RB [ processes ]]
  114. X.SH DESCRIPTION
  115. X.I Team
  116. Xjust copies its standard input to its standard output. It does so
  117. Xhowever forking a team of independent
  118. X.I processes
  119. X(default is 4), arranged in a ring, with reads overlapped
  120. Xwith writes.
  121. X.LP
  122. XEach process will wait for the end of the read phase of
  123. Xprevious process, will then read
  124. X.I blocksize
  125. Xbytes (or 512 byte blocks if suffixed with
  126. X.B b
  127. Xor kilobytes if suffixed with
  128. X.BR k ,
  129. Xor megabytes if suffixed with
  130. X.BR m ;
  131. Xthe default is
  132. X.BR 20k )
  133. Xfrom its standard input, activate the next process read
  134. Xphase, wait for the previous process write phase end, then
  135. Xwrite to its standard output, and activate the next process
  136. Xwrite phase.
  137. X.LP
  138. XIf the input or output volume ends or an IO error is detected,
  139. X.I team
  140. Xwill ask wehtether the user wants to continue (but only if the
  141. Xconcerned volume is a block or character device and the program's
  142. Xstandard error channel is a tty device). Possible answers are
  143. X.TP
  144. X.B c
  145. XThis means that the user wishes to continue, but the file pointer shall be
  146. Xreset to the beginning of the volume, as a new volume has been started
  147. Xanew. This is the the answer to give on end of volume when writing to
  148. Xmultiple floppies, etc...
  149. X.TP
  150. X.B y
  151. XThe user simply wishes to continue, without any change.
  152. X.TP
  153. X.B n
  154. XThe user wishes to stop; the program will be terminated or aborted.
  155. X.LP
  156. XThere are just three options, as follows:
  157. X.TP
  158. X.B -i
  159. XThe value that follows the option is the assumed volume size of the input,
  160. Xand it is expressed in the same units as the buffer size.
  161. X.TP
  162. X.B -o
  163. XThe value that follows the option is the assumed volume size of the output,
  164. Xand it is expressed in the same units as the buffer size.
  165. X.TP
  166. X.B -v
  167. XIf specified as each buffer is read or written the total number of kilobytes
  168. Xread or written that far is printed.
  169. X.TP
  170. X.B -r
  171. XIf specified the number of kilobytes processed and the number of seconds
  172. Xtaken is
  173. X.B not
  174. Xprinted at the end of the run.
  175. X.LP
  176. X.I Team
  177. Xconsumes system time to synchronize and task switch among
  178. Xits processes; also, in order to avoid slowing it, it is
  179. Xbest run on a quiescent system.
  180. X.LP
  181. XThis program is most useful for output to a device, especially
  182. Xwhere a streaming tape is involved. It may be used to advantage with
  183. Xdisc to disc and disc to tape copies.
  184. X.SH EXAMPLES
  185. Xfind dir -print | cpio -oBc | team 20k 8 >/dev/rmt0
  186. X.br
  187. Xteam 20k 4 </dev/rmt0 | cpio -iBcdmu
  188. X.br
  189. Xpax -w -b 4k * | team -o 1200k 15k 2 >/dev/rdsk/f0t
  190. X.SH ADVICE
  191. XYou are advised to experiment with different combinations of block size and
  192. Xnumber of processes; each program used with
  193. X.I team
  194. Xworks best with certain parameters, and performance depends even more
  195. Xstrongly on the output device, so experiment with parameters also for
  196. Xthis (it seems that the blocking factor of the process that feeds
  197. X.I team
  198. Xought to be inferior to that given to it, and possibly inferior to the
  199. Xlimit on the size of a pipe for your version of the system).
  200. X.I Team
  201. Xought to be adaptive, and adjust dynamically both parameters, in order to
  202. Xreach a state where there is no pause between each stage of the ring. This is
  203. Xtoo difficult to achieve under UNIX.
  204. X.LP
  205. XNotice also that this program will read and write blocks all of the
  206. Xsame size as prescribed, except the last, even when reading from
  207. Xpipes; if a read from its input supplies less bytes than the prescribed
  208. Xblock size, this program will read again until its buffer is filled
  209. Xto norm or the input finishes.
  210. X.LP
  211. XA final note: it is usually advantageous to give to
  212. X.I team
  213. Xa block size that is a multiple of the block size produced by the
  214. Xprogram before it in a pipeline. Notice that in many cases, such
  215. Xas the tape archival programs, the output will not be directly
  216. Xrecognizable to the tape archiver in input, but will have to be reblocked
  217. Xback to the blocksize expected by the tape archiver either by way
  218. Xof
  219. X.I dd
  220. Xor reapplication of
  221. X.IR team ,
  222. Xthat is much faster of course.
  223. X.SH BUGS
  224. X.I Team
  225. Xwill emit a number of messages comprehensible only to the author in case
  226. Xof errors. Plase note them and report them to the author.
  227. X.LP
  228. XThis is not strictly a bug in this program, but rather a limitation; some
  229. Xdevice drivers will have problems when you change volume when this program
  230. Xasks you whether to continue operation. They require that the device be
  231. Xclosed and opened again whenever a volume is changed. Unfortunately this
  232. Xcannot be done, given the structure of
  233. X.IR team ;
  234. Xwith such device drivers you effectively cannot use team to write multiple
  235. Xvolumes.
  236. X.LP
  237. XSome device drivers, on physical end of file or volume while writing do
  238. Xnot do the decent thing, and write a legible truncated block and return
  239. Xits length; some drivers, e.g. some tape drivers, handle physical eof
  240. Xon write quite badly. With these drivers you had better use the
  241. X.B -o
  242. Xoption to set a logical EOF if you want to use multiple volumes. As an hint,
  243. Xgive the volume size as about five percent less than the nominal. Whatever
  244. Xvalue you use for output, take not of it, as you will have to use exactly the
  245. Xsame value for input!
  246. X.SH SEE ALSO
  247. X.IR volcopy (8)
  248. X.br
  249. X.IR cpio (1)
  250. X.br
  251. X.IR tar (1)
  252. X.br
  253. X.IR dump (8)
  254. X.SH AUTHOR
  255. XPiercarlo Grandi, Milano.
  256. END_OF_FILE
  257. if test 5406 -ne `wc -c <'team.1'`; then
  258.     echo shar: \"'team.1'\" unpacked with wrong size!
  259. fi
  260. # end of 'team.1'
  261. fi
  262. if test -f 'team.c' -a "${1}" != "-c" ; then 
  263.   echo shar: Will not clobber existing file \"'team.c'\"
  264. else
  265. echo shar: Extracting \"'team.c'\" \(23313 characters\)
  266. sed "s/^X//" >'team.c' <<'END_OF_FILE'
  267. X/*
  268. X  $Header: /fs/mumon/aware/piercarl/Cmd./Commands./team.c,v 3.1 1994/01/13 15:03:25 piercarl Exp piercarl $
  269. X*/
  270. X
  271. Xstatic char Notice[] =
  272. X  "Copyright 1987,1989 Piercarlo Grandi. All rights reserved.";
  273. X
  274. X/*
  275. X  This program is free software; you can redistribute it and/or
  276. X  modify it under the terms of the GNU General Public License as
  277. X  published by the Free Software Foundation; either version 2, or
  278. X  (at your option) any later version.
  279. X
  280. X  This program is distributed in the hope that it will be useful,
  281. X  but WITHOUT ANY WARRANTY; without even the implied warranty of
  282. X  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  283. X  GNU General Public License for more details.
  284. X
  285. X  You may have received a copy of the GNU General Public License
  286. X  along with this program; if not, write to the Free Software
  287. X  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  288. X*/
  289. X
  290. X#undef DEBUG
  291. X
  292. X/*
  293. X  Unix programs normally do synchronous read and write, that is,
  294. X  you read and then you write; no overlap is possible.
  295. X
  296. X  This is especially catastrophic for device to device copies,
  297. X  whereit is important to minimize elapsed time, by overlapping
  298. X  activity on one with activity on another.
  299. X
  300. X  To obtain this, a multiprocess structure is necessary under
  301. X  Unix.  This program is functionally equivalento to a pipe, in
  302. X  that it copies its input (fd 0) to its output (fd 1) link.
  303. X
  304. X  This programs is executed as a Team of N processes, called
  305. X  Guys, all of which share the same input and output links; the
  306. X  first reads a chunk of input, awakens the second, writes the
  307. X  chunk to its output; the second does the same, and the last
  308. X  awakens the first.
  309. X
  310. X  Since this process is essentially cyclic, we use a ring of
  311. X  pipes to synchronize the Guys.  Each guy has un input pipe from
  312. X  the upstream guy and an output pipe to the downstream guy.
  313. X  Whenever a guy receives a READ command from the upstream, it
  314. X  first reads a block and then passes on the READ command
  315. X  downstream; it then waits for a WRITE command from upstream,
  316. X  and then writes the block and after that passes the WRITE
  317. X  command downstream. A count of how much has been processed is
  318. X  also passwd along, for statistics and verification.
  319. X
  320. X  Two other commands are used, one is STOP, and is sent
  321. X  downstream from the guy that detects the end of file of the
  322. X  input, after which the guy exits, and ABORT, which is sent
  323. X  downstream from the guy which detects trouble in the guy
  324. X  upstream to it, which has much the same effect.
  325. X*/
  326. X
  327. X#define TeamLVOLSZ    (1L<<10)
  328. X#define TeamHVOLSZ    ((long unsigned) 3 * ((long unsigned) 1 << 30))
  329. X
  330. X#define TeamLBUFSZ    (64)        /* Low buffer size        */
  331. X#define TeamDBUFSZ    (60*512)    /* Default buffer size        */
  332. X#define TeamHBUFSZ    (1L<<20)    /* High buffer size        */
  333. X
  334. X#define TeamDTEAMSZ    4        /* Default # of processes    */
  335. X#define TeamHTEAMSZ    16        /* High # of processes        */
  336. X
  337. X/*
  338. X  External components...  Probably the only system dependent part
  339. X  of this program, as some systems have something in
  340. X  /usr/include/sys where others have it in /usr/include.
  341. X
  342. X  Also, the mesg() procedure is highly system dependent...  watch
  343. X  out for locking and variable number of arguments.
  344. X*/
  345. X
  346. X#include <errno.h>
  347. X#include <signal.h>
  348. X#include <stdio.h>
  349. X#include <sys/types.h>
  350. X#include <sys/file.h>
  351. X#include <sys/stat.h>
  352. X#include <fcntl.h>
  353. X
  354. X#ifdef sun
  355. X# undef F_SETLKW
  356. X#endif
  357. X
  358. X#if (PCG)
  359. X# include "Extend.h"
  360. X# include "Here.h"
  361. X# include "Type.h"
  362. X#else
  363. X# define call        (void)
  364. X# define were        if
  365. X# define fast        register
  366. X# define global         /* extern */
  367. X# define local        static
  368. X# define when        break; case
  369. X# define otherwise    break; default
  370. X# define mode(which,name) typedef which name name; which name
  371. X# define bool    int
  372. X# define true            1
  373. X# define false            0
  374. X# define nil(type)    ((type) 0)
  375. X# define scalar            int
  376. X  typedef char            *pointer;
  377. X# if (defined(SMALL_M))
  378. X    typedef unsigned    address;      
  379. X# else
  380. X    typedef long    address;
  381. X# endif
  382. X# if (__STDC__)
  383. X#   define of(list)    list
  384. X#   define on(list)
  385. X#   define is(list)    (list)
  386. X#   define _        ,
  387. X#   define noparms    void
  388. X# else
  389. X#   define void     int
  390. X#   define const    /* const */
  391. X#   define of(list)    ()
  392. X#   define on(list)    list
  393. X#   define is(list)    list;
  394. X#   define _        ;
  395. X#   define noparms
  396. X# endif
  397. X#endif
  398. X
  399. X#ifdef DEBUG
  400. X# define Mesg(list) mesg list
  401. X#else
  402. X# define Mesg(list)
  403. X#endif
  404. X
  405. X/*VARARGS1*/
  406. Xmesg(a,b,c,d,e,f,g,h,i)
  407. X  char *a;
  408. X  int b,c,d,e,f,g,h,i;
  409. X{
  410. X# if (defined F_SETLKW)
  411. X    struct flock l;
  412. X    l.l_whence = 0; l.l_start = 0L; l.l_len = 0L;
  413. X    l.l_type = F_WRLCK; fcntl(fileno(stderr),F_SETLKW,&l);
  414. X# endif
  415. X# if (defined LOCK_EX)
  416. X    flock(fileno(stderr),LOCK_EX);
  417. X# endif
  418. X  fprintf(stderr,a,b,c,d,e,f,g,h,i);
  419. X# if (defined LOCK_EX)
  420. X    flock(fileno(stderr),LOCK_UN);
  421. X# endif
  422. X# if (defined F_SETLKW)
  423. X    l.l_type = F_UNLCK; fcntl(fileno(stderr),F_SETLKW,&l);
  424. X# endif
  425. X}
  426. X
  427. Xlocal bool        verbose = false;
  428. Xlocal bool        report = true;
  429. X
  430. Xextern int        errno;
  431. Xlocal time_t        origin;
  432. X
  433. Xextern time_t        time of((time_t *));
  434. Xextern int        atoi of((const char *));
  435. Xextern char        *malloc of((unsigned));
  436. Xextern char        *calloc of((address,unsigned));
  437. Xextern char        *strchr of((const char *,int));
  438. X
  439. Xextern int        getopt of((int,char *[],const char *));
  440. Xextern char        *optarg;
  441. Xextern int        optind;
  442. X
  443. X/*
  444. X  The  regular Unix read and write calls are not guaranteed to process
  445. X  all  the  bytes  requested.  These  procedures guarantee that if the
  446. X  request is for N bytes, all of them are read or written unless there
  447. X  is an error or eof.
  448. X*/
  449. X
  450. X#define FdCLOSED    0
  451. X#define FdOPEN        1
  452. X#define FdEOF         2
  453. X#define FdERROR     3
  454. X
  455. Xmode(struct,Fd)
  456. X{
  457. X  int         fd;
  458. X  short        status;
  459. X  long unsigned   size;
  460. X};
  461. X
  462. Xlocal Fd        FdIn,FdOut;
  463. X
  464. Xlocal bool        FdOpen on((fd,ffd,size)) is
  465. X(
  466. X  fast Fd          *fd
  467. X_ int              ffd
  468. X_ long unsigned          size
  469. X)
  470. X{
  471. X  fd->status = (ffd >= 0) ? FdOPEN :   FdCLOSED;
  472. X  fd->fd    = ffd;
  473. X  fd->size    = size;
  474. X
  475. X  Mesg(("FdOpen fd %d\n",ffd));
  476. X
  477. X  return ffd >= 0;
  478. X}
  479. X
  480. Xlocal bool        FdClose on((fd)) is
  481. X(
  482. X  fast Fd          *fd
  483. X)
  484. X{
  485. X  int               ffd;
  486. X
  487. X  ffd = fd->fd;
  488. X  Mesg(("FdClose fd %d\n",fd->fd));
  489. X  fd->status = FdCLOSED;
  490. X  fd->fd = -1;
  491. X
  492. X  return close(ffd) >= 0;
  493. X}
  494. X
  495. Xlocal bool        FdCopy on((to,from)) is
  496. X(
  497. X  fast Fd          *to
  498. X_ fast Fd          *from
  499. X)
  500. X{
  501. X  to->size    = from->size;
  502. X  to->status    = from->status;
  503. X  to->fd    = dup(from->fd);
  504. X  Mesg(("FdCopy of %d is %d\n",from->fd,to->fd));
  505. X  return to->fd >= 0;
  506. X}
  507. X
  508. Xlocal void        FdSet on((to,from)) is
  509. X(
  510. X  fast Fd          *to
  511. X_ fast Fd          *from
  512. X)
  513. X{
  514. X  if (from->fd < 0)
  515. X    mesg("team: set an invalid fd\n");
  516. X  to->size    = from->size;
  517. X  to->status    = from->status;
  518. X  to->fd    = from->fd;
  519. X}
  520. X
  521. Xlocal long unsigned    FdRetry on((fd,which,done,space)) is
  522. X(
  523. X  fast Fd          *fd
  524. X_ char              *which
  525. X_ long unsigned          done
  526. X_ long unsigned          space
  527. X)
  528. X{
  529. X  int              tty;
  530. X  char              reply[2];
  531. X  struct stat          st;
  532. X
  533. X  if (fstat(fd->fd,&st) < 0)
  534. X  {
  535. X    perror(which);
  536. X    return 0;
  537. X  }
  538. X
  539. X  st.st_mode &= S_IFMT;
  540. X  if (st.st_mode != S_IFCHR && st.st_mode != S_IFBLK)
  541. X    return 0;
  542. X
  543. X  if (!isatty(fileno(stderr)))
  544. X    return 0;
  545. X
  546. X  if ((tty = open("/dev/tty",0)) < 0)
  547. X  {
  548. X    perror("/dev/tty");
  549. X    return 0;
  550. X  }
  551. X
  552. X  do
  553. X  {
  554. X#if (defined i386 || defined sun)
  555. X    extern char        *(sys_errlist[]);
  556. X    char        *errmsg = sys_errlist[errno];
  557. X#else
  558. X    char        errmsg[32];
  559. X    (void) sprintf(errmsg,"Error %d",errno);
  560. X#endif
  561. X    if (errno)
  562. X      mesg("'%s' on %s after %luk. Continue [cyn] ? ",errmsg,which,done>>10);
  563. X    else
  564. X      mesg("EOF on %s after %luk. Continue [cyn] ? ",which,done>>10);
  565. X
  566. X    read(tty,reply,sizeof reply);
  567. X  }
  568. X  while (strchr("cCyYnN",reply[0]) == 0);
  569. X
  570. X  call close(tty);
  571. X
  572. X  if (strchr("nN",reply[0]) != 0)
  573. X    return 0L;
  574. X
  575. X  errno = 0;
  576. X
  577. X  if (strchr("cC",reply[0]) != 0)
  578. X  {
  579. X    call lseek(fd->fd,0L,0);
  580. X    return fd->size;
  581. X  }
  582. X
  583. X  return space;
  584. X}    
  585. X
  586. Xlocal unsigned        FdCanDo on((remaining,available)) is
  587. X(
  588. X  fast address          remaining
  589. X_ fast long unsigned      available
  590. X)
  591. X{
  592. X  return (remaining < available)
  593. X    ? (unsigned) remaining : (unsigned) available;
  594. X}
  595. X
  596. Xlocal address        FdRead on((fd,buffer,todo,done)) is
  597. X(
  598. X  fast Fd          *fd
  599. X_ pointer          buffer
  600. X_ fast address          todo
  601. X_ long unsigned          done
  602. X)
  603. X{
  604. X  fast long unsigned      space;
  605. X  fast int          bytesRead;
  606. X  fast address          justDone;
  607. X
  608. X  switch (fd->status)
  609. X  {
  610. X  when FdEOF:     return 0;
  611. X  when FdERROR:   return -1;
  612. X  when FdCLOSED:  return -1;
  613. X
  614. X  when FdOPEN:
  615. X
  616. X    space = fd->size - done%fd->size;
  617. X
  618. X    for (justDone = 0; space != 0L && justDone < todo;)
  619. X    {
  620. X      bytesRead = read(fd->fd,buffer+justDone,
  621. X         FdCanDo(todo-justDone,space-justDone));
  622. X
  623. X      if (bytesRead <= 0 || (justDone += bytesRead) == space)
  624. X    space = FdRetry(fd,"input",done+justDone,space-justDone);
  625. X    }
  626. X
  627. X    if (bytesRead == 0) fd->status = FdEOF;
  628. X    if (bytesRead < 0)    fd->status = FdERROR;
  629. X
  630. X    Mesg(("FdRead %d reads %d last %d\n",fd->fd,justDone,bytesRead));
  631. X
  632. X    return (justDone == 0) ? bytesRead : justDone;
  633. X  }
  634. X  /*NOTREACHED*/
  635. X}
  636. X
  637. Xlocal address        FdWrite on((fd,buffer,todo,done)) is
  638. X(
  639. X  fast Fd          *fd
  640. X_ pointer          buffer
  641. X_ fast address          todo
  642. X_ long unsigned          done
  643. X)
  644. X{
  645. X  fast long unsigned      space;
  646. X  fast int          bytesWritten;
  647. X  fast address          justDone;
  648. X
  649. X  switch (fd->status)
  650. X  {
  651. X  when FdEOF:     return 0;
  652. X  when FdERROR:   return -1;
  653. X  when FdCLOSED:  return -1;
  654. X
  655. X  when FdOPEN:
  656. X
  657. X    space = fd->size - done%fd->size;
  658. X
  659. X    for (justDone = 0; space != 0L && justDone < todo;)
  660. X    {
  661. X      bytesWritten = write(fd->fd,buffer+justDone,
  662. X          FdCanDo(todo-justDone,space-justDone));
  663. X
  664. X      if (bytesWritten <= 0 || (justDone += bytesWritten) == space)
  665. X    space = FdRetry(fd,"output",done+justDone,space-justDone);
  666. X    }
  667. X
  668. X    Mesg(("FdWrite %d writes %d last %d\n",fd->fd,justDone,bytesWritten));
  669. X
  670. X    if (bytesWritten == 0)  fd->status =   FdEOF;
  671. X    if (bytesWritten < 0)   fd->status =   FdERROR;
  672. X
  673. X    return (justDone == 0) ? bytesWritten : justDone;
  674. X  }
  675. X  /*NOTREACHED*/
  676. X}
  677. X
  678. X/*
  679. X  A Token is scalar   value   representing a command.
  680. X*/
  681. X
  682. Xtypedef short scalar Token;
  683. X
  684. X#define TokenREAD   0
  685. X#define TokenWRITE  1
  686. X#define TokenSTOP   2
  687. X#define TokenABORT  -1
  688. X
  689. X/*
  690. X  Here we represent Streams as Fds; this is is not entirely
  691. X  appropriate, as Fds have also a volume size, and relatively
  692. X  high overhead write and read functions.  Well, we just take
  693. X  some liberties with abstraction levels here.  Actually we
  694. X  should have an Fd abstraction for stream pipes and a Vol
  695. X  abstraction for input and output...
  696. X*/
  697. X
  698. Xlocal bool        StreamPipe on((downstream,upstream)) is
  699. X(
  700. X  fast Fd          *downstream
  701. X_ fast Fd          *upstream
  702. X)
  703. X{
  704. X  int              links[2];
  705. X
  706. X  if (pipe(links) < 0)
  707. X  {
  708. X    perror("team: opening links");
  709. X    return false;
  710. X  }
  711. X
  712. X  Mesg(("StreamPipe fd downstream %d upstream %d\n",links[1],links[0]));
  713. X
  714. X  return FdOpen(downstream,links[1],TeamHVOLSZ)
  715. X    && FdOpen(upstream,links[0],TeamHVOLSZ);
  716. X}
  717. X
  718. Xmode(struct,StreamMsg)
  719. X{
  720. X  Token              token;
  721. X  short              status;
  722. X  long unsigned          done;
  723. X};
  724. X
  725. Xlocal bool        StreamSend on((fd,token,status,done)) is
  726. X(
  727. X  fast Fd          *fd
  728. X_ Token           token
  729. X_ short           status
  730. X_ long unsigned          done
  731. X)
  732. X{
  733. X  fast int          n;
  734. X  StreamMsg          message;
  735. X
  736. X  message.token = token;
  737. X  message.status = status;
  738. X  message.done = done;
  739. X
  740. X  n = write(fd->fd,(pointer) &message,(unsigned) sizeof message);
  741. X
  742. X  Mesg(("StreamSend fd %u n %d token %d\n",fd->fd,n,token));
  743. X
  744. X  return n == sizeof message;
  745. X}
  746. X
  747. Xlocal bool        StreamReceive on((fd,tokenp,statusp,donep)) is
  748. X(
  749. X  fast Fd          *fd
  750. X_ Token           *tokenp
  751. X_ short           *statusp
  752. X_ long unsigned          *donep
  753. X)
  754. X{
  755. X  fast int          n;
  756. X  StreamMsg          message;
  757. X
  758. X  n = read(fd->fd,(pointer) &message,(unsigned) sizeof message);
  759. X  *tokenp = message.token;
  760. X  *statusp = message.status;
  761. X  *donep = message.done;
  762. X
  763. X  Mesg(("StreamReceive fd %u n %d token %d\n",fd->fd,n,*tokenp));
  764. X
  765. X  return n == sizeof message;
  766. X}
  767. X/*
  768. X  A guy is an instance of the input to output copier. It is attached
  769. X  to a relay station, with an upstream link, from which commands
  770. X  arrive, and a downward link, to which they are relayed once they are
  771. X  executed.
  772. X*/
  773. X
  774. Xmode(struct,Guy)
  775. X{
  776. X  int              pid;
  777. X  Fd              upStream;
  778. X  Fd              downStream;
  779. X};
  780. X
  781. Xlocal bool        GuyOpen on((guy,pid,upstream,downstream)) is
  782. X(
  783. X  fast Guy          *guy
  784. X_ int              pid
  785. X_ Fd              *upstream
  786. X_ Fd              *downstream
  787. X)
  788. X{
  789. X  Mesg(("GuyOpen pid %u upstream %u downstream %u\n",
  790. X    pid,upstream->fd,downstream->fd));
  791. X
  792. X  guy->pid = pid;
  793. X  FdSet(&guy->upStream,upstream);
  794. X  FdSet(&guy->downStream,downstream);
  795. X
  796. X  return true;
  797. X}
  798. X
  799. X#define GuySEND(guy,token,status,done)   \
  800. X  StreamSend(&guy->downStream,token,status,done)
  801. X
  802. X#define GuyRECEIVE(guy,tokenp,statusp,donep) \
  803. X  StreamReceive(&guy->upStream,tokenp,statusp,donep)
  804. X
  805. Xlocal bool        GuyStop of((Guy *,char *,long unsigned));
  806. X
  807. Xlocal bool        GuyStart on((guy,bufsize)) is
  808. X(
  809. X  fast Guy          *guy
  810. X_ address          bufsize
  811. X)
  812. X{
  813. X  fast char          *buffer;
  814. X  Token           token;
  815. X  short           status;
  816. X  long unsigned          done;
  817. X  bool          received;
  818. X  static int           bytesRead,bytesWritten;
  819. X
  820. X  Mesg(("GuyStart guy %#o bufsize %u\n",guy,bufsize));
  821. X
  822. X  buffer = (pointer) malloc((unsigned) bufsize);
  823. X  if (buffer == nil(pointer))
  824. X  {
  825. X    mesg("team: guy %d cannot allocate %u bytes\n",
  826. X      guy->pid,bufsize);
  827. X    return false;
  828. X  }
  829. X
  830. X  while ((received = GuyRECEIVE(guy,&token,&status,&done)) && token != TokenSTOP)
  831. X  switch (token)
  832. X  {
  833. X  when TokenREAD:
  834. X    FdIn.status = status;
  835. X
  836. X    Mesg(("GuyStart reading %d chars\n",bufsize));
  837. X    bytesRead = FdRead(&FdIn,(pointer) buffer,bufsize,done);
  838. X    Mesg(("GuyStart reads %d chars\n",bytesRead));
  839. X
  840. X    if (bytesRead == 0) GuyStop(guy,nil(char *),done);
  841. X    if (bytesRead < 0) GuyStop(guy,"error on guy read",done);
  842. X
  843. X    done += bytesRead;
  844. X
  845. X    if (verbose)
  846. X      mesg("%luk read   \r",done>>10);
  847. X
  848. X    if (!GuySEND(guy,TokenREAD,FdIn.status,done))
  849. X      GuyStop(guy,"guy cannot send READ",done);
  850. X
  851. X  when TokenWRITE:
  852. X    FdOut.status = status;
  853. X
  854. X    Mesg(("GuyStart writing %d chars\n",bytesRead));
  855. X    bytesWritten = FdWrite(&FdOut,(pointer) buffer,(address) bytesRead,done);
  856. X    Mesg(("GuyStart writes %d chars\n",bytesWritten));
  857. X
  858. X    if (bytesWritten == 0)  GuyStop(guy,"eof on guy write",done);
  859. X    if (bytesWritten < 0)   GuyStop(guy,"error on guy write",done);
  860. X
  861. X    done += bytesWritten;
  862. X
  863. X    if (verbose)
  864. X      mesg("%luk written\r",done>>10);
  865. X
  866. X    if (!GuySEND(guy,TokenWRITE,FdOut.status,done))
  867. X      GuyStop(guy,"guy cannot send WRITE",done);
  868. X
  869. X  when TokenABORT:
  870. X    GuyStop(guy,"guy was aborted",0L);
  871. X
  872. X  otherwise:
  873. X    GuyStop(guy,"impossible token on ring",done);
  874. X  }
  875. X
  876. X  /* free((char *) buffer); */
  877. X
  878. X  GuyStop(guy,(received) ? nil(char *) : "error on upstream receive",0L);
  879. X  /*NOTREACHED*/
  880. X
  881. X  /*return true;*/
  882. X}
  883. X
  884. Xlocal bool        GuyStop on((guy,errormsg,done)) is
  885. X(
  886. X  fast Guy          *guy
  887. X_ char              *errormsg
  888. X_ long unsigned          done
  889. X)
  890. X{
  891. X  Mesg(("GuyStop guy %#o\n",guy));
  892. X
  893. X  if (done)
  894. X  {
  895. X    if (report)
  896. X      mesg("%lu kilobytes, %lu seconds\r\n",
  897. X      done>>10,(long unsigned) (time((time_t *) 0)-origin));
  898. X    else if (verbose)
  899. X      mesg("\n");
  900. X  }
  901. X
  902. X  if (errormsg != nil(char *))
  903. X  {
  904. X    mesg("team: guy pid %u: %s\n",guy->pid,errormsg);
  905. X    call GuySEND(guy,TokenABORT,FdERROR,0L);
  906. X    exit(1);
  907. X    /*NOTREACHED*/
  908. X  }
  909. X
  910. X  if (!GuySEND(guy,TokenSTOP,FdEOF,0L))
  911. X  {
  912. X    exit(1);
  913. X    /*NOTREACHED*/
  914. X  }
  915. X
  916. X  exit(0);
  917. X  /*NOTREACHED*/
  918. X}
  919. X
  920. Xlocal bool        GuyClose on((guy)) is
  921. X(
  922. X  fast Guy          *guy
  923. X)
  924. X{
  925. X  return FdClose(&guy->upStream) && FdClose(&guy->downStream);
  926. X}
  927. X
  928. X/*
  929. X  A team is made up of a ring of guys; each guy copies a blockfrom its
  930. X  input to its ouput, and is driven by tokens sent to it by the
  931. X  previous guy on a pipe.
  932. X*/
  933. X
  934. Xmode(struct,Team)
  935. X{
  936. X  Guy              *guys;
  937. X  short unsigned      size;
  938. X  short unsigned      active;
  939. X};
  940. X
  941. Xlocal bool        TeamOpen on((team,nominalsize)) is
  942. X(
  943. X  Team              *team
  944. X_ short unsigned      nominalsize
  945. X)
  946. X{
  947. X  Mesg(("TeamOpen nominalsize %u\n",nominalsize));
  948. X
  949. X  team->size     = 0;
  950. X  team->active   = 0;
  951. X
  952. X  team->guys = (Guy *) calloc(sizeof (Guy),nominalsize);
  953. X
  954. X  for (team->size = 0; team->size < nominalsize; team->size++);
  955. X
  956. X  were (team->guys == nil(Guy *))
  957. X    return false;
  958. X
  959. X  return true;
  960. X}
  961. X
  962. Xlocal bool        TeamStart on((team,bufsize,isize,osize)) is
  963. X(
  964. X  fast Team          *team
  965. X_ address          bufsize
  966. X_ long unsigned          isize
  967. X_ long unsigned          osize
  968. X)
  969. X{
  970. X  /*
  971. X    When generating each guy, we pass it an upstream link that
  972. X    is the downstream of the previous guy, and create a new
  973. X    downstream link that will be the next upstream.
  974. X
  975. X    At each turn we obviously close the old downstream once it
  976. X    has been passed to the forked guy.
  977. X
  978. X    A special case are the first and last guys; the upstreamof
  979. X    the first guy shall be the downstream of the last.  This
  980. X    goes against the grain of our main logic, where the
  981. X    upstream is expected to already exist and the downstream
  982. X    must be created.
  983. X
  984. X    This means that the last and first guys are created in a
  985. X    special way.  When creating the first guy we shall create
  986. X    its upstreamlink as well as its downstream, and we shall
  987. X    save that in a special variable, last_downstream.  This we
  988. X    shall use as the downstreamof the last guy.
  989. X
  990. X    We shall also keep it open in the team manager (parent
  991. X    process) because we shall use it to do the initial send of
  992. X    the read and write tokens that will circulate in the relay
  993. X    ring, activating the guys.
  994. X
  995. X    Of course because of this each guy will inherit this link
  996. X    as wellas its upstream and downstream, but shall graciously
  997. X    close it.
  998. X  */
  999. X
  1000. X  Fd             last_downstream;
  1001. X  Fd             this_upstream;
  1002. X  Fd             this_downstream;
  1003. X  Fd             next_upstream;
  1004. X
  1005. X  Mesg(("TeamStart team %#o size %u bufsize %u\n",
  1006. X    team,team->size,bufsize));
  1007. X
  1008. X  call FdOpen(&FdIn,0,isize); call FdOpen(&FdOut,1,osize);
  1009. X
  1010. X  for (team->active = 0; team->active < team->size; team->active++)
  1011. X  {
  1012. X    fast Guy        *guy;
  1013. X    fast int         pid;
  1014. X
  1015. X    guy = team->guys+team->active;
  1016. X
  1017. X    if (team->active == 0)
  1018. X    {
  1019. X      if (!StreamPipe(&last_downstream,&this_upstream))
  1020. X      {
  1021. X    perror("cannot open first link");
  1022. X    return false;
  1023. X      }
  1024. X
  1025. X      if (!StreamPipe(&this_downstream,&next_upstream))
  1026. X      {
  1027. X    perror("cannot  open link");
  1028. X    return false;
  1029. X      }
  1030. X    }
  1031. X    else if (team->active < (team->size-1))
  1032. X    {
  1033. X      if (!StreamPipe(&this_downstream,&next_upstream))
  1034. X      {
  1035. X    perror("cannot  open link");
  1036. X    return  false;
  1037. X      }
  1038. X    }
  1039. X    else /*if (team->active == team->size-1)*/
  1040. X    {
  1041. X      FdSet(&this_downstream,&last_downstream);
  1042. X      if (!FdCopy(&last_downstream,&this_downstream))
  1043. X    perror("team: cannot copy last downstream");
  1044. X    }
  1045. X
  1046. X    Mesg(("TeamStart going to fork for guy %#o\n",guy));
  1047. X
  1048. X    pid = fork();
  1049. X
  1050. X    if (pid > 0)
  1051. X    {
  1052. X      Mesg(("TeamStart forked guy %#o as pid %u\n",guy,pid));
  1053. X      guy->pid = pid;
  1054. X
  1055. X      if (!FdClose(&this_upstream))
  1056. X    perror("cannot close this upstream link");
  1057. X      if (!FdClose(&this_downstream))
  1058. X    perror("cannot close this downstream link");
  1059. X
  1060. X      FdSet(&this_upstream,&next_upstream);
  1061. X    }
  1062. X    else if (pid == 0)
  1063. X    {
  1064. X      pid = getpid();
  1065. X
  1066. X      if (!FdClose(&last_downstream))
  1067. X    perror("cannot close inherited first link");
  1068. X
  1069. X      if (!GuyOpen(guy,pid,&this_upstream,&this_downstream))
  1070. X    GuyStop(guy,"cannot open guy",0L);
  1071. X      if (!GuyStart(guy,bufsize))
  1072. X    GuyStop(guy,"cannot start guy",0L);
  1073. X      if (!GuyClose(guy))
  1074. X    perror("cannot close guy");
  1075. X
  1076. X      /*NOTREACHED*/
  1077. X    }
  1078. X    else if (pid < 0)
  1079. X    {
  1080. X      perror("team: forking a guy");
  1081. X      return false;
  1082. X    }
  1083. X  }
  1084. X
  1085. X  if (!StreamSend(&last_downstream,TokenREAD,FdOPEN,0L))
  1086. X  {
  1087. X    perror("cannot send first READ token");
  1088. X    return false;
  1089. X  }
  1090. X
  1091. X  if (!StreamSend(&last_downstream,TokenWRITE,FdOPEN,0L))
  1092. X  {
  1093. X    perror("cannot send first WRITE token");
  1094. X    return false;
  1095. X  }
  1096. X
  1097. X  if (!FdClose(&last_downstream))
  1098. X    perror("cannot close first link");
  1099. X
  1100. X  return true;
  1101. X}
  1102. X
  1103. Xlocal bool        TeamWait on((team)) is
  1104. X(
  1105. X  fast Team            *team
  1106. X)
  1107. X{
  1108. X  while (team->active != 0)
  1109. X  {
  1110. X    int             guypid;
  1111. X    int             status;
  1112. X
  1113. X    guypid = wait(&status);
  1114. X    if (guypid >= 0)
  1115. X    {
  1116. X      fast short unsigned guyno;
  1117. X
  1118. X      for (guyno = 0; guyno < team->size; guyno++)
  1119. X    if (guypid == team->guys[guyno].pid)
  1120. X    {
  1121. X      team->guys[guyno].pid = -1;
  1122. X      break;
  1123. X    }
  1124. X    }
  1125. X    else
  1126. X    {
  1127. X      mesg("team: no guys, believed %u left\n",team->active);
  1128. X      return true;
  1129. X    }
  1130. X
  1131. X    --team->active;
  1132. X
  1133. X    if (status != 0   && team->active != 0)
  1134. X      return false;
  1135. X  }
  1136. X
  1137. X  return true;
  1138. X}
  1139. X
  1140. Xlocal bool        TeamStop on((team)) is
  1141. X(
  1142. X  fast Team          *team
  1143. X)
  1144. X{
  1145. X  fast short unsigned      guyno;
  1146. X
  1147. X  Mesg(("TeamStop team %#o\n",team));
  1148. X
  1149. X  for (guyno = 0; guyno < team->size; guyno++)
  1150. X  {
  1151. X    fast Guy            *guy;
  1152. X
  1153. X    guy = team->guys+guyno;
  1154. X    if (guy->pid >= 0)
  1155. X    {
  1156. X      /*kill(guy->pid,SIGKILL);*/
  1157. X      --team->active;
  1158. X    }
  1159. X  }
  1160. X
  1161. X  return team->active == 0;
  1162. X}
  1163. X
  1164. Xlocal bool        TeamClose on((team)) is
  1165. X(
  1166. X  fast Team          *team
  1167. X)
  1168. X{
  1169. X  for (team->size; team->size != 0; --team->size)
  1170. X    continue;
  1171. X
  1172. X  free(team->guys);
  1173. X
  1174. X  return true;
  1175. X}
  1176. X
  1177. Xlocal void        usage of((noparms))
  1178. X{
  1179. X  fprintf(stderr,"\
  1180. Xsyntax: team [-[vr]] [-iI[bkm] [-oO[bkm] [N[bkm] [P]]\n\
  1181. X  copies standard input to output\n\
  1182. X  -v gives ongoing report, -r final report\n\
  1183. X  I is input volume size (default %lum)\n\
  1184. X  O is output volume size (default %lum)\n\
  1185. X  N is buffer size (default %luk)\n\
  1186. X  P is number of processes (default %u)\n\
  1187. X  (postfix b means *512, k means *1KB, m means *1MB)\n\
  1188. X",
  1189. X      TeamHVOLSZ>>20,TeamHVOLSZ>>20,
  1190. X      TeamDBUFSZ>>10,TeamDTEAMSZ);
  1191. X
  1192. X  exit(1);
  1193. X  /*NOTREACHED*/
  1194. X}
  1195. X
  1196. Xlocal long unsigned    atos on((s)) is
  1197. X(
  1198. X  fast char          *s
  1199. X)
  1200. X{
  1201. X  fast unsigned long      l;
  1202. X
  1203. X  for (
  1204. X    s, l = 0L;
  1205. X    *s >= '0' && *s <= '9';
  1206. X    s++
  1207. X  )
  1208. X    l = l*10L + (long unsigned) (*s-'0');
  1209. X
  1210. X  if (*s == 'b') l *= (1L<<9);
  1211. X  if (*s == 'k') l *= (1L<<10);
  1212. X  if (*s == 'm') l *= (1L<<20);
  1213. X
  1214. X  return l;
  1215. X}
  1216. X
  1217. Xglobal int        main on((argc,argv)) is
  1218. X(
  1219. X  int              argc
  1220. X_ char              *(argv[])
  1221. X)
  1222. X{
  1223. X  Team              team;
  1224. X  short unsigned      teamsize;
  1225. X
  1226. X  address          bufsize;
  1227. X  long unsigned          isize;
  1228. X  long unsigned          osize;
  1229. X  int              opt;
  1230. X
  1231. X  teamsize = TeamDTEAMSZ;
  1232. X  bufsize = TeamDBUFSZ;
  1233. X  isize = TeamHVOLSZ;
  1234. X  osize = TeamHVOLSZ;
  1235. X  optind = 1;
  1236. X
  1237. X  while ((opt = getopt(argc,argv,"vri:o:")) != -1)
  1238. X    switch (opt)
  1239. X    {
  1240. X    when 'i':
  1241. X      isize = atos(optarg);
  1242. X      if (isize < TeamLVOLSZ || isize > TeamHVOLSZ)
  1243. X      {
  1244. X    fprintf(stderr,"team: invalid input volume size %lu\n",isize);
  1245. X    usage();
  1246. X      }
  1247. X  
  1248. X    when 'o':
  1249. X      osize = atos(optarg);
  1250. X      if (osize < TeamLVOLSZ || osize > TeamHVOLSZ)
  1251. X      {
  1252. X    fprintf(stderr,"team: invalid output volume size %lu\n",osize);
  1253. X    usage();
  1254. X      }
  1255. X
  1256. X    when 'v':
  1257. X      verbose ^= 1;
  1258. X
  1259. X    when 'r':
  1260. X      report ^= 1;
  1261. X  
  1262. X    otherwise:
  1263. X      usage();
  1264. X    }
  1265. X
  1266. X  argc -= optind, argv += optind;
  1267. X
  1268. X  if (argc != 0)
  1269. X  {
  1270. X    bufsize = (address) atos(argv[0]);
  1271. X    if (bufsize < TeamLBUFSZ || bufsize > TeamHBUFSZ)
  1272. X    {
  1273. X      fprintf(stderr,"team: invalid block size %u\n",
  1274. X    bufsize);
  1275. X      usage();
  1276. X    }
  1277. X    --argc, argv++;
  1278. X  }
  1279. X
  1280. X  if (argc != 0)
  1281. X  {
  1282. X    teamsize = atoi(argv[0]);
  1283. X    if (teamsize < 2 || teamsize > TeamHTEAMSZ)
  1284. X    {
  1285. X      fprintf(stderr,"team: invalid # of processes %d\n",teamsize);
  1286. X      usage();
  1287. X    }
  1288. X    --argc, argv++;
  1289. X  }
  1290. X
  1291. X  if (argc != 0)   usage();
  1292. X    
  1293. X  if (!TeamOpen(&team,teamsize))
  1294. X  {
  1295. X    mesg("team: cannot setup the team with %u guys\n",teamsize);
  1296. X    return 1;
  1297. X  }
  1298. X
  1299. X  origin = time((time_t *) 0);
  1300. X
  1301. X  if (!TeamStart(&team,bufsize,isize,osize))
  1302. X  {
  1303. X    mesg("team: cannot start the team\n");
  1304. X    return 1;
  1305. X  }
  1306. X
  1307. X  if (!TeamWait(&team))
  1308. X  {
  1309. X    mesg("team: stop remaining %u guys\n",team.active);
  1310. X
  1311. X    if (!TeamStop(&team))
  1312. X    {
  1313. X      mesg("team: cannot stop the team\n");
  1314. X      return 1;
  1315. X    }
  1316. X  }
  1317. X
  1318. X  if (!TeamClose(&team))
  1319. X  {
  1320. X    mesg("team: cannot close the team\n");
  1321. X    return 1;
  1322. X  }
  1323. X
  1324. X  return 0;
  1325. X}
  1326. END_OF_FILE
  1327. if test 23313 -ne `wc -c <'team.c'`; then
  1328.     echo shar: \"'team.c'\" unpacked with wrong size!
  1329. fi
  1330. # end of 'team.c'
  1331. fi
  1332. echo shar: End of shell archive.
  1333. exit 0
  1334.