147082Smckusick /*- 247082Smckusick * Copyright (c) 1980, 1991 The Regents of the University of California. 347082Smckusick * All rights reserved. 447082Smckusick * 547082Smckusick * %sccs.include.redist.c% 622040Sdist */ 722040Sdist 817527Ssam #ifndef lint 9*48621Skarels static char sccsid[] = "@(#)tape.c 5.18 (Berkeley) 04/24/91"; 1046585Storek #endif /* not lint */ 1117527Ssam 1246795Sbostic #include <sys/param.h> 1346585Storek #include <sys/wait.h> 1446795Sbostic #include <ufs/dir.h> 1546795Sbostic #include <ufs/dinode.h> 1646795Sbostic #include <ufs/fs.h> 1746795Sbostic #include <signal.h> 1846795Sbostic #include <fcntl.h> 1946795Sbostic #include <protocols/dumprestore.h> 2046585Storek #include <errno.h> 2146795Sbostic #ifdef __STDC__ 2246795Sbostic #include <unistd.h> 2346795Sbostic #include <stdlib.h> 2446795Sbostic #include <string.h> 2546795Sbostic #endif 2646795Sbostic #include "dump.h" 2739128Smckusick #include "pathnames.h" 281425Sroot 2929899Smckusick char (*tblock)[TP_BSIZE]; /* pointer to malloc()ed buffer for tape */ 3029899Smckusick int writesize; /* size of malloc()ed buffer for tape */ 3129899Smckusick long lastspclrec = -1; /* tape block number of last written header */ 3229899Smckusick int trecno = 0; /* next record to write in current block */ 3346614Smckusick extern long blocksperfile; /* number of blocks per output file */ 34*48621Skarels long blocksthisvol; /* number of blocks on current output file */ 35*48621Skarels extern int ntrec; /* blocking factor on tape */ 36*48621Skarels extern int cartridge; 3747056Skarels char *nexttape; 3825219Smckusick #ifdef RDUMP 3925219Smckusick extern char *host; 4046585Storek int rmtopen(), rmtwrite(); 4146585Storek void rmtclose(); 4225219Smckusick #endif RDUMP 431425Sroot 4446585Storek int atomic(); 4546789Smckusick void doslave(), enslave(), flushtape(), killall(); 4646585Storek 4710911Ssam /* 4824181Smckusick * Concurrent dump mods (Caltech) - disk block reading and tape writing 4918012Smckusick * are exported to several slave processes. While one slave writes the 5018012Smckusick * tape, the others read disk blocks; they pass control of the tape in 5124181Smckusick * a ring via flock(). The parent process traverses the filesystem and 5246789Smckusick * sends writeheader()'s and lists of daddr's to the slaves via pipes. 5310911Ssam */ 5418012Smckusick struct req { /* instruction packets sent to slaves */ 5518012Smckusick daddr_t dblk; 5618012Smckusick int count; 5718012Smckusick } *req; 5818012Smckusick int reqsiz; 5918012Smckusick 6024181Smckusick #define SLAVES 3 /* 1 slave writing, 1 reading, 1 for slack */ 6125219Smckusick int slavefd[SLAVES]; /* pipes from master to each slave */ 6225219Smckusick int slavepid[SLAVES]; /* used by killall() */ 6325219Smckusick int rotor; /* next slave to be instructed */ 6425219Smckusick int master; /* pid of master, for sending error signals */ 6525219Smckusick int tenths; /* length of tape used per block written */ 6618012Smckusick 6746585Storek int 6810911Ssam alloctape() 6910911Ssam { 7025219Smckusick int pgoff = getpagesize() - 1; 7110911Ssam 7210911Ssam writesize = ntrec * TP_BSIZE; 7325219Smckusick reqsiz = ntrec * sizeof(struct req); 7424181Smckusick /* 7525219Smckusick * CDC 92181's and 92185's make 0.8" gaps in 1600-bpi start/stop mode 7625219Smckusick * (see DEC TU80 User's Guide). The shorter gaps of 6250-bpi require 7725219Smckusick * repositioning after stopping, i.e, streaming mode, where the gap is 7825219Smckusick * variable, 0.30" to 0.45". The gap is maximal when the tape stops. 7924181Smckusick */ 8047056Skarels if (blocksperfile == 0) 8147056Skarels tenths = writesize / density + 8247056Skarels (cartridge ? 16 : density == 625 ? 5 : 8); 8325219Smckusick /* 8425219Smckusick * Allocate tape buffer contiguous with the array of instruction 8546789Smckusick * packets, so flushtape() can write them together with one write(). 8625219Smckusick * Align tape buffer on page boundary to speed up tape write(). 8725219Smckusick */ 8824181Smckusick req = (struct req *)malloc(reqsiz + writesize + pgoff); 8924181Smckusick if (req == NULL) 9024181Smckusick return(0); 9124181Smckusick tblock = (char (*)[TP_BSIZE]) (((long)&req[ntrec] + pgoff) &~ pgoff); 9225219Smckusick req = (struct req *)tblock - ntrec; 9324181Smckusick return(1); 9410911Ssam } 9510911Ssam 9625219Smckusick 9746585Storek void 9846789Smckusick writerec(dp) 995329Smckusic char *dp; 1001425Sroot { 10118012Smckusick req[trecno].dblk = (daddr_t)0; 10218012Smckusick req[trecno].count = 1; 10324181Smckusick *(union u_spcl *)(*tblock++) = *(union u_spcl *)dp; /* movc3 */ 10429899Smckusick lastspclrec = spcl.c_tapea; 10524181Smckusick trecno++; 1061425Sroot spcl.c_tapea++; 10746585Storek if (trecno >= ntrec) 10846789Smckusick flushtape(); 1091425Sroot } 1101425Sroot 11146585Storek void 11246789Smckusick dumpblock(blkno, size) 1134774Smckusic daddr_t blkno; 1144774Smckusic int size; 1151425Sroot { 11625219Smckusick int avail, tpblks, dblkno; 1171425Sroot 1185329Smckusic dblkno = fsbtodb(sblock, blkno); 11946585Storek tpblks = size >> tp_bshift; 12018012Smckusick while ((avail = MIN(tpblks, ntrec - trecno)) > 0) { 12118012Smckusick req[trecno].dblk = dblkno; 12218012Smckusick req[trecno].count = avail; 12325219Smckusick trecno += avail; 1244774Smckusic spcl.c_tapea += avail; 12525219Smckusick if (trecno >= ntrec) 12646789Smckusick flushtape(); 12746585Storek dblkno += avail << (tp_bshift - dev_bshift); 1285329Smckusic tpblks -= avail; 1294774Smckusic } 1301425Sroot } 1311425Sroot 1321425Sroot int nogripe = 0; 1331425Sroot 13446585Storek void 13546585Storek tperror() 13646585Storek { 13718012Smckusick if (pipeout) { 13846614Smckusick msg("write error on %s\n", tape); 13946585Storek quit("Cannot recover\n"); 14018012Smckusick /* NOTREACHED */ 14118012Smckusick } 142*48621Skarels msg("write error %d blocks into volume %d\n", blocksthisvol, tapeno); 14346614Smckusick broadcast("DUMP WRITE ERROR!\n"); 14418012Smckusick if (!query("Do you want to restart?")) 14518012Smckusick dumpabort(); 14646614Smckusick msg("Closing this volume. Prepare to restart with new media;\n"); 14718012Smckusick msg("this dump volume will be rewritten.\n"); 14824181Smckusick killall(); 14918012Smckusick nogripe = 1; 15018012Smckusick close_rewind(); 15118012Smckusick Exit(X_REWRITE); 15218012Smckusick } 15318012Smckusick 15446585Storek void 15525219Smckusick sigpipe() 15625219Smckusick { 15725219Smckusick 15846585Storek quit("Broken pipe\n"); 15925219Smckusick } 16025219Smckusick 16146585Storek void 16246789Smckusick flushtape() 16318012Smckusick { 16446795Sbostic #ifndef __STDC__ 16546795Sbostic int write(); 16646795Sbostic #endif 16746795Sbostic 16825219Smckusick int siz = (char *)tblock - (char *)req; 1691425Sroot 17046585Storek if (atomic(write, slavefd[rotor], req, siz) != siz) 17146585Storek quit("error writing command pipe: %s\n", strerror(errno)); 17246585Storek if (++rotor >= SLAVES) 17346585Storek rotor = 0; 17418012Smckusick tblock = (char (*)[TP_BSIZE]) &req[ntrec]; 1751425Sroot trecno = 0; 17624181Smckusick asize += tenths; 17710911Ssam blockswritten += ntrec; 178*48621Skarels blocksthisvol += ntrec; 17946614Smckusick if (!pipeout && (blocksperfile ? 180*48621Skarels (blocksthisvol >= blocksperfile) : (asize > tsize))) { 1811425Sroot close_rewind(); 18246789Smckusick startnewtape(); 1831425Sroot } 1841425Sroot timeest(); 1851425Sroot } 1861425Sroot 18746585Storek void 18846239Storek trewind() 1891425Sroot { 19024181Smckusick int f; 19112331Smckusick 19212331Smckusick if (pipeout) 19312331Smckusick return; 19418012Smckusick for (f = 0; f < SLAVES; f++) 19518012Smckusick close(slavefd[f]); 19646585Storek while (wait((int *)NULL) >= 0) /* wait for any signals from slaves */ 19746585Storek /* void */; 198*48621Skarels msg("Closing %s\n", tape); 19918012Smckusick #ifdef RDUMP 20025219Smckusick if (host) { 20125219Smckusick rmtclose(); 20225219Smckusick while (rmtopen(tape, 0) < 0) 20325219Smckusick sleep(10); 20425219Smckusick rmtclose(); 20525219Smckusick return; 20625219Smckusick } 20725219Smckusick #endif RDUMP 20846789Smckusick close(tapefd); 2093214Swnj while ((f = open(tape, 0)) < 0) 2103214Swnj sleep (10); 2113214Swnj close(f); 2121425Sroot } 2131425Sroot 21446585Storek void 2151425Sroot close_rewind() 2161425Sroot { 21746239Storek trewind(); 218*48621Skarels if (nexttape) 219*48621Skarels return; 22018012Smckusick if (!nogripe) { 22146614Smckusick msg("Change Volumes: Mount volume #%d\n", tapeno+1); 22246614Smckusick broadcast("CHANGE DUMP VOLUMES!\7\7\n"); 2231425Sroot } 224*48621Skarels while (!query("Is the new volume mounted and ready to go?")) 22525219Smckusick if (query("Do you want to abort?")) { 2261425Sroot dumpabort(); 22725219Smckusick /*NOTREACHED*/ 22825219Smckusick } 2291425Sroot } 2301425Sroot 2311425Sroot /* 23218012Smckusick * We implement taking and restoring checkpoints on the tape level. 2331425Sroot * When each tape is opened, a new process is created by forking; this 2341425Sroot * saves all of the necessary context in the parent. The child 2351425Sroot * continues the dump; the parent waits around, saving the context. 2361425Sroot * If the child returns X_REWRITE, then it had problems writing that tape; 2371425Sroot * this causes the parent to fork again, duplicating the context, and 2381425Sroot * everything continues as if nothing had happened. 2391425Sroot */ 2401425Sroot 24146585Storek void 24246789Smckusick startnewtape() 2431425Sroot { 2441425Sroot int parentpid; 2451425Sroot int childpid; 2461425Sroot int status; 2471425Sroot int waitpid; 24839164Sbostic sig_t interrupt; 24929899Smckusick int blks, i; 25047056Skarels char *p; 2511425Sroot 25239164Sbostic interrupt = signal(SIGINT, SIG_IGN); 2531425Sroot parentpid = getpid(); 2541425Sroot 2551425Sroot restore_check_point: 25639164Sbostic (void)signal(SIGINT, interrupt); 25725219Smckusick /* 25825219Smckusick * All signals are inherited... 25925219Smckusick */ 2601425Sroot childpid = fork(); 26118012Smckusick if (childpid < 0) { 2621425Sroot msg("Context save fork fails in parent %d\n", parentpid); 2631425Sroot Exit(X_ABORT); 2641425Sroot } 26518012Smckusick if (childpid != 0) { 2661425Sroot /* 2671425Sroot * PARENT: 2681425Sroot * save the context by waiting 2691425Sroot * until the child doing all of the work returns. 27018012Smckusick * don't catch the interrupt 2711425Sroot */ 27225219Smckusick signal(SIGINT, SIG_IGN); 2731425Sroot #ifdef TDEBUG 2741425Sroot msg("Tape: %d; parent process: %d child process %d\n", 2751425Sroot tapeno+1, parentpid, childpid); 2761425Sroot #endif TDEBUG 27718012Smckusick while ((waitpid = wait(&status)) != childpid) 27818012Smckusick msg("Parent %d waiting for child %d has another child %d return\n", 27918012Smckusick parentpid, childpid, waitpid); 28018012Smckusick if (status & 0xFF) { 2811425Sroot msg("Child %d returns LOB status %o\n", 2821425Sroot childpid, status&0xFF); 2831425Sroot } 2841425Sroot status = (status >> 8) & 0xFF; 2851425Sroot #ifdef TDEBUG 28618012Smckusick switch(status) { 2871425Sroot case X_FINOK: 2881425Sroot msg("Child %d finishes X_FINOK\n", childpid); 2891425Sroot break; 2901425Sroot case X_ABORT: 2911425Sroot msg("Child %d finishes X_ABORT\n", childpid); 2921425Sroot break; 2931425Sroot case X_REWRITE: 2941425Sroot msg("Child %d finishes X_REWRITE\n", childpid); 2951425Sroot break; 2961425Sroot default: 29718012Smckusick msg("Child %d finishes unknown %d\n", 29825219Smckusick childpid, status); 2991425Sroot break; 3001425Sroot } 3011425Sroot #endif TDEBUG 30218012Smckusick switch(status) { 3031425Sroot case X_FINOK: 3041425Sroot Exit(X_FINOK); 3051425Sroot case X_ABORT: 3061425Sroot Exit(X_ABORT); 3071425Sroot case X_REWRITE: 3081425Sroot goto restore_check_point; 3091425Sroot default: 3101425Sroot msg("Bad return code from dump: %d\n", status); 3111425Sroot Exit(X_ABORT); 3121425Sroot } 3131425Sroot /*NOTREACHED*/ 3141425Sroot } else { /* we are the child; just continue */ 3151425Sroot #ifdef TDEBUG 3161425Sroot sleep(4); /* allow time for parent's message to get out */ 3171425Sroot msg("Child on Tape %d has parent %d, my pid = %d\n", 3181425Sroot tapeno+1, parentpid, getpid()); 31925219Smckusick #endif TDEBUG 32047056Skarels /* 32147056Skarels * If we have a name like "/dev/rmt0,/dev/rmt1", 32247056Skarels * use the name before the comma first, and save 323*48621Skarels * the remaining names for subsequent volumes. 32447056Skarels */ 325*48621Skarels tapeno++; /* current tape sequence */ 326*48621Skarels if (nexttape || index(tape, ',')) { 327*48621Skarels if (nexttape && *nexttape) 328*48621Skarels tape = nexttape; 329*48621Skarels if (p = index(tape, ',')) { 330*48621Skarels *p = '\0'; 331*48621Skarels nexttape = p + 1; 332*48621Skarels } else 333*48621Skarels nexttape = NULL; 334*48621Skarels msg("Dumping volume %d on %s\n", tapeno, tape); 335*48621Skarels } 33618012Smckusick #ifdef RDUMP 33746789Smckusick while ((tapefd = (host ? rmtopen(tape, 2) : 33846789Smckusick pipeout ? 1 : open(tape, O_WRONLY|O_CREAT, 0666))) < 0) 33925219Smckusick #else RDUMP 34046789Smckusick while ((tapefd = 34146789Smckusick pipeout ? 1 : open(tape, O_WRONLY|O_CREAT, 0666)) < 0) 34224181Smckusick #endif RDUMP 34339128Smckusick { 34446614Smckusick msg("Cannot open output \"%s\".\n", tape); 34539128Smckusick if (!query("Do you want to retry the open?")) 34618012Smckusick dumpabort(); 34739128Smckusick } 3481425Sroot 34918012Smckusick enslave(); /* Share open tape file descriptor with slaves */ 35018012Smckusick 3511425Sroot asize = 0; 352*48621Skarels blocksthisvol = 0; 3531425Sroot newtape++; /* new tape signal */ 35429899Smckusick blks = 0; 35529899Smckusick if (spcl.c_type != TS_END) 35629899Smckusick for (i = 0; i < spcl.c_count; i++) 35729899Smckusick if (spcl.c_addr[i] != 0) 35829899Smckusick blks++; 35929899Smckusick spcl.c_count = blks + 1 - spcl.c_tapea + lastspclrec; 3601425Sroot spcl.c_volume++; 3611425Sroot spcl.c_type = TS_TAPE; 36230432Smckusick spcl.c_flags |= DR_NEWHEADER; 36346789Smckusick writeheader(curino); 36430432Smckusick spcl.c_flags &=~ DR_NEWHEADER; 3651425Sroot if (tapeno > 1) 366*48621Skarels msg("Volume %d begins with blocks from inode %d\n", 36746789Smckusick tapeno, curino); 3681425Sroot } 3691425Sroot } 3701425Sroot 37146585Storek void 3721425Sroot dumpabort() 3731425Sroot { 37418012Smckusick if (master != 0 && master != getpid()) 37525219Smckusick kill(master, SIGTERM); /* Signals master to call dumpabort */ 37624181Smckusick else { 37724181Smckusick killall(); 37824181Smckusick msg("The ENTIRE dump is aborted.\n"); 37924181Smckusick } 3801425Sroot Exit(X_ABORT); 3811425Sroot } 3821425Sroot 38346585Storek void 3841425Sroot Exit(status) 38546239Storek int status; 3861425Sroot { 3871425Sroot #ifdef TDEBUG 3881425Sroot msg("pid = %d exits with status %d\n", getpid(), status); 3891425Sroot #endif TDEBUG 3901925Swnj exit(status); 3911425Sroot } 39218012Smckusick 39324181Smckusick /* 39425219Smckusick * could use pipe() for this if flock() worked on pipes 39524181Smckusick */ 39646585Storek void 39724181Smckusick lockfile(fd) 39824181Smckusick int fd[2]; 39924181Smckusick { 40024181Smckusick char tmpname[20]; 40118012Smckusick 40239128Smckusick strcpy(tmpname, _PATH_LOCK); 40324181Smckusick mktemp(tmpname); 40446585Storek if ((fd[1] = creat(tmpname, 0400)) < 0) 40546585Storek quit("cannot create lockfile %s: %s\n", 40646585Storek tmpname, strerror(errno)); 40746585Storek if ((fd[0] = open(tmpname, 0)) < 0) 40846585Storek quit("cannot reopen lockfile %s: %s\n", 40946585Storek tmpname, strerror(errno)); 41046585Storek (void) unlink(tmpname); 41124181Smckusick } 41224181Smckusick 41346585Storek void 41418012Smckusick enslave() 41518012Smckusick { 41624181Smckusick int first[2], prev[2], next[2], cmd[2]; /* file descriptors */ 41724181Smckusick register int i, j; 41818012Smckusick 41918012Smckusick master = getpid(); 42025219Smckusick signal(SIGTERM, dumpabort); /* Slave sends SIGTERM on dumpabort() */ 42125219Smckusick signal(SIGPIPE, sigpipe); 42225219Smckusick signal(SIGUSR1, tperror); /* Slave sends SIGUSR1 on tape errors */ 42324181Smckusick lockfile(first); 42424181Smckusick for (i = 0; i < SLAVES; i++) { 42524181Smckusick if (i == 0) { 42624181Smckusick prev[0] = first[1]; 42724181Smckusick prev[1] = first[0]; 42824181Smckusick } else { 42924181Smckusick prev[0] = next[0]; 43024181Smckusick prev[1] = next[1]; 43124181Smckusick flock(prev[1], LOCK_EX); 43224181Smckusick } 43326485Smckusick if (i < SLAVES - 1) { 43426485Smckusick lockfile(next); 43526485Smckusick } else { 43626485Smckusick next[0] = first[0]; 43726485Smckusick next[1] = first[1]; /* Last slave loops back */ 43826485Smckusick } 43946585Storek if (pipe(cmd) < 0 || (slavepid[i] = fork()) < 0) 44046585Storek quit("too many slaves, %d (recompile smaller): %s\n", 44146585Storek i, strerror(errno)); 44218012Smckusick slavefd[i] = cmd[1]; 44325219Smckusick if (slavepid[i] == 0) { /* Slave starts up here */ 44418012Smckusick for (j = 0; j <= i; j++) 44518012Smckusick close(slavefd[j]); 44625219Smckusick signal(SIGINT, SIG_IGN); /* Master handles this */ 44725219Smckusick doslave(cmd[0], prev, next); 44818012Smckusick Exit(X_FINOK); 44918012Smckusick } 45018012Smckusick close(cmd[0]); 45124181Smckusick if (i > 0) { 45224181Smckusick close(prev[0]); 45324181Smckusick close(prev[1]); 45424181Smckusick } 45518012Smckusick } 45624181Smckusick close(first[0]); 45724181Smckusick close(first[1]); 45824181Smckusick master = 0; rotor = 0; 45918012Smckusick } 46018012Smckusick 46146585Storek void 46224181Smckusick killall() 46318012Smckusick { 46424181Smckusick register int i; 46519982Smckusick 46624181Smckusick for (i = 0; i < SLAVES; i++) 46724181Smckusick if (slavepid[i] > 0) 46824181Smckusick kill(slavepid[i], SIGKILL); 46918012Smckusick } 47018012Smckusick 47124181Smckusick /* 47224181Smckusick * Synchronization - each process has a lockfile, and shares file 47324181Smckusick * descriptors to the following process's lockfile. When our write 47424181Smckusick * completes, we release our lock on the following process's lock- 47524181Smckusick * file, allowing the following process to lock it and proceed. We 47624181Smckusick * get the lock back for the next cycle by swapping descriptors. 47724181Smckusick */ 47846585Storek void 47925219Smckusick doslave(cmd, prev, next) 48025219Smckusick register int cmd, prev[2], next[2]; 48119982Smckusick { 48225219Smckusick register int nread, toggle = 0; 483*48621Skarels int nwrite; 48446795Sbostic #ifndef __STDC__ 48546795Sbostic int read(); 48646795Sbostic #endif 48719982Smckusick 48846789Smckusick /* 48946789Smckusick * Need our own seek pointer. 49046789Smckusick */ 49146789Smckusick close(diskfd); 49246789Smckusick if ((diskfd = open(disk, O_RDONLY)) < 0) 49346585Storek quit("slave couldn't reopen disk: %s\n", strerror(errno)); 49424181Smckusick /* 49525219Smckusick * Get list of blocks to dump, read the blocks into tape buffer 49624181Smckusick */ 49725219Smckusick while ((nread = atomic(read, cmd, req, reqsiz)) == reqsiz) { 49818012Smckusick register struct req *p = req; 49918012Smckusick for (trecno = 0; trecno < ntrec; trecno += p->count, p += p->count) { 50018012Smckusick if (p->dblk) { 50118012Smckusick bread(p->dblk, tblock[trecno], 50225219Smckusick p->count * TP_BSIZE); 50318012Smckusick } else { 50425219Smckusick if (p->count != 1 || atomic(read, cmd, 50546585Storek tblock[trecno], TP_BSIZE) != TP_BSIZE) 50646585Storek quit("master/slave protocol botched.\n"); 50718012Smckusick } 50818012Smckusick } 50924181Smckusick flock(prev[toggle], LOCK_EX); /* Wait our turn */ 51025219Smckusick 51118012Smckusick #ifdef RDUMP 512*48621Skarels if ((nwrite = (host ? rmtwrite(tblock[0], writesize) 513*48621Skarels : write(tapefd, tblock[0], writesize))) != writesize) { 51425219Smckusick #else RDUMP 515*48621Skarels if ((nwrite = write(tapefd, tblock[0], writesize)) 516*48621Skarels != writesize) { 51724181Smckusick #endif RDUMP 518*48621Skarels if (nwrite == -1) 519*48621Skarels perror("write"); 520*48621Skarels else 521*48621Skarels msg("short write: got %d instead of %d\n", 522*48621Skarels nwrite, writesize); 52325219Smckusick kill(master, SIGUSR1); 52425219Smckusick for (;;) 52525219Smckusick sigpause(0); 52618012Smckusick } 52724181Smckusick toggle ^= 1; 52824181Smckusick flock(next[toggle], LOCK_UN); /* Next slave's turn */ 52924181Smckusick } /* Also jolts him awake */ 53046585Storek if (nread != 0) 53146585Storek quit("error reading command pipe: %s\n", strerror(errno)); 53218012Smckusick } 53319947Smckusick 53419947Smckusick /* 53525219Smckusick * Since a read from a pipe may not return all we asked for, 53625219Smckusick * or a write may not write all we ask if we get a signal, 53725219Smckusick * loop until the count is satisfied (or error). 53819947Smckusick */ 53946585Storek int 54025219Smckusick atomic(func, fd, buf, count) 54125219Smckusick int (*func)(), fd, count; 54219947Smckusick char *buf; 54319947Smckusick { 54425219Smckusick int got, need = count; 54519947Smckusick 54625219Smckusick while ((got = (*func)(fd, buf, need)) > 0 && (need -= got) > 0) 54719947Smckusick buf += got; 54825219Smckusick return (got < 0 ? got : count - need); 54919947Smckusick } 550