/* This file is in the public domain. */ /* * BSD disklabel editor. * * Usage: $0 [options] [dev] * * where "dev" can be a disk device (eg, /dev/rsd0c) or a file holding * an image of of the disk. * * Options (this is a second copy which can drift out of sync with * reality; see handleargs() for the real truth): * * -disk dev * Specify the disk device. This is just like specifying * the disk device as above, but is unambiguous if the * device begins, or may begin, with a -. * * -fixmagic * Ignore the magic number in the label, replacing it with * a valid magic number. This is a dangerous option, * since the magic number is one of the safeguards against * beliving arbitrary trash. * * You do not need -fixmagic if the only problem is that * the label was written by a machine of opposite * endianness; bsdlabel knows how to byteswap. * * -ro * Run read-only. Do not open dev for write. * * -fixsum * Ignore the checksum in the label on read, recomputing * it as usual on write. This is a dangerous option, * since the checksum is one of the safeguards against * beliving arbitrary trash. * * -new * This starts with a new, empty, label rather than using * what it finds on the disk. If you want to label a new * disk, it is better to use this rather than combining * -fixmagic and -fixsum and cleaning up the "label" * resulting from believing whatever happens to be on the * disk. * * -q * Suppresses commentary and prompts. This is designed to * make scripting use of bsdlabel easier. * * -endian kind * Specifies the endianness to expect. kind can be * * guess Recognize either endianness's magic number and * deduce the endianness from it. This is the * default. * native Label endianness must match the local host's. * swap Label endianness must be swapped from the local * host's. * big Label must be big-endian. * little Label must be little-endian. * * Except for `guess' mode, a label of the wrong * endianness will not be considered a valid label. When * no valid magic number was read from the disk (when * -new was used, or when -fixmagic was used and no * suitable magic number was found) this also specifies * which endianness to write; `guess' turns into `native' * in this case. * * -block B * Specifies that the label is expected in block B. By * default, B is zero (bsdlabel looks for the label in the * first sector). The correct value for B depends on the * architecture for which the disk is intended; see that * port's LABELSECTOR define (also available via the * kern.labelsector sysctl in recent kernels). * * -offset O * Specifies that the label is offset O bytes from the * beginning of the sector it's found in (see -block). By * default, O is zero (the label is at the very beginning * of the sector). The correct value for O depends on the * architecture for which the disk is intended; see that * port's LABELOFFSET define (also available via the * kern.labeloffset sysctl in recent kernels). * * Type a ? at the prompt for help on what commands are available * there. */ /* * In the comments below, the "disk device" does not actually need to * be a disk device; it may, for example, be a file containing a disk * image. However, constantly writing things like "the disk device or * file containing the disk image" is cumbersome. */ #ifdef DISTRIB /* * For systems which don't provide __progname, build with -DDISTRIB, to * turn on this code (which provides its own __progname). */ const char *__progname; int main(int, char **); int main_(int, char **); int main(int ac, char **av) { __progname = av[0]; main_(ac,av); } #define main main_ #endif /* * The code backing the S command (set the label in the kernel) is * currently specific to NetBSD, as far as I know. If you have * another system with a sufficiently similar DIOCSDINFO interface, * you can build with -DS_COMMAND to turn the relevant code on * anyway. */ #ifdef __NetBSD__ #define S_COMMAND #endif /* * Include files. Mostly neither here nor there, but one is used only * if we're building with the S command. */ #include #include #include #include #include #include #include #include #include #include #ifdef S_COMMAND #include #endif /* * There's a design misfeature in . It's fine for its * apparent use, which is testing "unsigned char or EOF" values such * as are returned by getc(). But when testing elements of a char * array, it's annoying, because char might be signed, in which case * we are outside the interface spec for the is*() functions/macros. * * So we use ISSPACE instead, which explicitly casts. (We'd have * others, except isspace() is the only ctype.h call we want to use.) */ #define ISSPACE(c) isspace((unsigned char)(c)) extern const char *__progname; /* * We want to mark unused arguments, for better error checking. The * only compiler I use which is capable of this is gcc; this test * tests for gcc, then tests for a version that supports the relevant * attribute. * * If you have some other compiler which is capable of some other such * mechanism, you might want to elaborate this test (though there's * not much point unless you're fiddling with the code). */ #if defined(__GNUC__) && \ ( (__GNUC__ > 2) || \ ( (__GNUC__ == 2) && \ defined(__GNUC_MINOR__) && \ (__GNUC_MINOR__ >= 7) ) ) #define UNUSED(x) x __attribute__ ((__unused__)) #else #define UNUSED(x) x #endif /* * The absolute maximum number of partitions we support. */ #define MAXNPART 22 /* * Convert from a (zero-origin) partition number to a partition letter * (eg, 5 becomes f - sd0f is partition #5 on sd0). */ #define PARTLETTER(i) ((i)+'a') /* * Convert from a partition letter to a (zero-origin) partition number. */ #define LETTERPART(i) ((i)-'a') /* * Typedefs for structs. See the individual struct definitions, below, * for more. */ typedef struct field FIELD; typedef struct label LABEL; typedef struct part PART; typedef struct twss TWSS; /* * A trailing whitespace stripper. We want this because it's more * convenient to generate lines with possibly-trailing whitespace, but * we don't want to actually print trailing whitespace. The paradigm * is that we wrap the FILE * we want to output to in another FILE *, * created with funopen(3), which handles the whitespace fiddling. * * Since the only form of trailing whitespace we generate is spaces, we * don't bother with any other kind. f is the FILE * we wrapped, the * one we want to output the stripped lines to; n is the number of * trailing spaces we have had printed to us but haven't yet passed * along. (We will either pass them along, if we get a non-whitespace * character before a newline, or throw them away, if a newline occurs * first.) */ struct twss { FILE *f; int n; } ; /* * A partition, as we represent it internally. start, size, and end * are the location of the partition, in whatever units the disklabel * uses (we have code elsewhere which assumes these are 512-octet * units, but this struct doesn't know that). type is the filesystem * type; as far as we are concerned, these are magic integers which * correspond to user-interface strings according to a table compiled * in here; see fstypes[]. fragsize, frag, and cpgsgs are values kept * in the label as hints for a few of the values necessary to create a * filesystem in the partition - an FFS filesystem, at least; other * filesystems sometimes keep other data here. These will normally be * written at filesystem creation time; we rarely need to set them. * We know about them both for the rare occasion when we _do_ want to * set them and to print them from existing labels. * * The UI actually represents fragsize (the fragment size) and frag * (the number of fragments per block) as fragsize and blocksize, * converting between that form and this form as necessary. */ struct part { unsigned long int start; unsigned long int size; unsigned long int end; unsigned long int fragsize; unsigned long int type; unsigned long int frag; unsigned long int cpgsgs; } ; /* * A disk label. Most of the fields here are directly equivalent to * similarly named fields from struct disklabel (though we don't * actually use struct disklabel except for the S command). * * The exceptions are dirty, which is a dirty bit, indicating that * something has been changed but the label hasn't yet been written * (this is used to make it more difficult to accidentally fail to * save changes), and parts[], which is our internal representation of * the partitions themselves (see struct part, above). */ struct label { /* BEGIN fields taken directly from struct disklabel */ unsigned long int type; unsigned long int subtype; char typename[16]; char packname[16]; unsigned long int secsize; unsigned long int nsect; unsigned long int ntrack; unsigned long int ncyl; unsigned long int spc; unsigned long int spu; unsigned long int spares_track; unsigned long int spares_cyl; unsigned long int acyl; unsigned long int rpm; unsigned long int interleave; unsigned long int trackskew; unsigned long int cylskew; unsigned long int headswitch; unsigned long int trkseek; unsigned long int flags; unsigned long int drivedata[5]; unsigned long int spare[5]; unsigned long int npart; unsigned long int bbsize; unsigned long int sbsize; /* END fields taken directly from struct disklabel */ int dirty; PART parts[MAXNPART]; } ; /* * When changing non-partition fields, we operate in a data-driven way. * This struct represents one field. * * tag is the short name of the field, used for user interaction. loc * points to the place (inside the global label struct) where the * corresponding data lives. match is used to tell whether a given * string from the user (passed as a pointer and length) matches this * field; if match is nil, a default equivalent to just comparing * against tag is used. This permits us to match fields like * "drivedata[3]" without needing to have one entry for each element * of the array; also, for some disk types, the names of the * drivedata[] elements changes. (See the comments on match_drdat and * print_drdat.) print is a function to print out the value. There * are only a few of these, one per data type, more or less. chval is * used to change the value; this exists because some fields do more * than just store the value. recompute, if non-nil, is called after * changing the field - this is not currently used by any fields and * arguably should be removed. Finally, taglen is the length of tag. * This is last, rather than next to tag, so that struct * initialization can easily omit it; rather than have the length * appear twice (once implicitly in the tag string and once in * taglen), we compute all the taglen values once at startup. */ struct field { const char *tag; void *loc; int (*match)(const char *, int); int (*print)(FIELD *, FILE *); void (*chval)(const char *, FIELD *); void (*recompute)(void); int taglen; } ; /* * Convenience macro, to give the number of elements in an array. * (Note: does not work for objects which are not arrays, even if they * are objects the [] syntax can be used with.) */ #define ARRAYSIZE(arr) (sizeof(arr)/sizeof(arr[0])) /* * This cannot be casually changed; it is known by everything else that * works with disklabels, the kernel in particular. */ #define LABEL_MAGIC 0x82564557 /* * File descriptor onto the disk device. This will normally be opened * RW, but may be RO if write access is denied or we were told to run * RO. */ static int diskfd; /* * The string name of the disk device. This is set when it's opened; * we save it mostly for error messages. This is not necessarily a * copy of the corresponding command-line argument; we attempt to be * friendly, accepting (eg) "sd3" to mean "the raw partition on sd3" * (/dev/rsd3c on most ports, /dev/rsd3d on i386 and a I think few * others saddled with peecee-style historical partition support). * diskname is the name we actually ended up opening rather than the * name provided by the user. */ static const char *diskname; /* * True if we're running readonly, false if read/write. This lets us * error on modification attempts, rather than allowing the change to * take place and delaying the error until we (try to) save. * * This is not quite the same as "-ro was given on the command line"; * it can also be forced on if we can't open the disk RW but can open * it RO. */ static int readonly; /* * Buffer for holding the label as read off - or written to - the disk. * This is used only during a read or write of the label; when * inspecting or changing the label, we use label (see below) instead. */ static unsigned char labelbuf[512]; /* * The label. See the comments on the correspnoding struct, above; * this is the only LABEL in the program, and exists for conceptual * collection of related information rather than because we have lots * of them running around - and as partial preparation for the day * when we might have multiple instances. */ static LABEL label; /* * Booleans indicating whether certain flags were given on the command * line, each variable commented with the relevant flag. See the * block usage comment at the top of this file for descriptions and, * in some cases, considerations applying to the use of these flags * from a user's perspective. */ static int fixmagic; /* -fixmagic */ static int fixcksum; /* -fixsum */ static int newlabel; /* -new */ static int quiet; /* -q */ /* * The maximum number of partitions we can support. This is not always * MAXNPART, from above; if -offset is given a nonzero value, there * may be space for fewer than MAXNPART partitions before the end of * the label area. We compute this based on the -offset value. (It * will never be larger than MAXNPART.) */ static int maxnpart; /* * The -block and -offset values (see the usage comment at the top of * this file). The values they are initialized to here are the values * we use if the corresponding options aren't given at all. */ static int label_block = 0; static int label_offset = 0; /* * Label endianness values. Values other than big, little, and guess * in the user interface are converted to one of these three by the * time they end up in this variable. */ #define END_GUESS 1 /* guess based on label */ #define END_BIG 2 /* big-endian */ #define END_LITTLE 3 /* little-endian */ static int endian = END_GUESS; /* * The list of non-partition label fields we know about. We * forward-declare the print and chval functions for all the fields, * plus any other functions which aren't nil. Most fields are either * 16- or 32-bit integers; such fields share print and chval functions * (the *_i16 and *_i32 versions for 16- and 32-bit values). There is * also the *_s16 functions; these are not signed 16-bit, but rather * max-16-octet strings. * * All recompute functions are currently nil; the recompute * functionality is not being used and, as noted above in the struct * definition's comment, arguably should be removed. */ static int print_i16(FIELD *, FILE *); static void chval_i16(const char *, FIELD *); static int print_i32(FIELD *, FILE *); static void chval_i32(const char *, FIELD *); static int print_s16(FIELD *, FILE *); static void chval_s16(const char *, FIELD *); static int print_type(FIELD *, FILE *); static void chval_type(const char *, FIELD *); static int match_drdat(const char *, int); static int print_flags(FIELD *, FILE *); static void chval_flags(const char *, FIELD *); static int print_drdat(FIELD *, FILE *); static void chval_drdat(const char *, FIELD *); static int print_spare(FIELD *, FILE *); static void chval_spare(const char *, FIELD *); static FIELD fields[] #define F16(name) { #name, &label.name, 0, print_i16, chval_i16, 0 } #define F32(name) { #name, &label.name, 0, print_i32, chval_i32, 0 } = { { "type", &label.type, 0, print_type, chval_type, 0 }, F16(subtype), { "typename", &label.typename[0], 0, print_s16, chval_s16, 0 }, { "packname", &label.packname[0], 0, print_s16, chval_s16, 0 }, F32(secsize), F32(nsect), F32(ntrack), F32(ncyl), F32(spc), F32(spu), F16(spares_track), F16(spares_cyl), F32(acyl), F16(rpm), F16(interleave), F16(trackskew), F16(cylskew), F32(headswitch), F32(trkseek), { "flags", &label.flags, 0, print_flags, chval_flags, 0 }, { "drivedata", &label.drivedata[0], match_drdat, print_drdat, chval_drdat, 0 }, { "spare", &label.spare[0], 0, print_spare, chval_spare, 0 }, F32(bbsize), F32(sbsize), { 0 } }; #undef F16 #undef F32 /* * Bits for the `flags' field's value. * * This struct is used in so few places and ways that it's not really * worth creating a typedef for it. */ static struct { const char *name; unsigned long int bit; } d_flags_bits[] = { { "removable", D_REMOVABLE }, { "ecc", D_ECC }, { "badsect", D_BADSECT }, { "ramdisk", D_RAMDISK }, { "chain", D_CHAIN }, { 0 } }; /* * The string forms of the values for the `type' label field. type * values are small-integer indexes into this table; anything outside * this range is printed as a number. */ static const char * const dktypes[] = { "unknown", "SMD", "MSCP", "old DEC", "SCSI", "ESDI", "ST506", "HP-IB", "HP-FL", "type 9", "floppy", "ccd", "vnd", "ATAPI", "RAID" }; #define NDKTYPES ARRAYSIZE(dktypes) static int dktypelens[NDKTYPES] = { -1 }; /* * The string forms of the values for a partition's type field. In * most cases, this is the kind of filesystem the partition supposedly * holds, but there are some cases where that description is * inaccurate (swap and RAID come to mind as examples: swap has no * filesystem and RAID may have multiple distinct filesystems in the * RAID volume's partitions). */ static const char * const fstypes[] = { "unused", "swap", "Version 6", "Version 7", "System V", "4.1BSD", "Eighth Edition", "4.2BSD", "MSDOS", "4.4LFS", "unknown", "HPFS", "ISO9660", "boot", "ADOS", "HFS", "FILECORE", "Linux Ext2", "NTFS", "RAID", "CCD", "JSF2", "Apple UFS", "Vinum" }; #define NFSTYPES ARRAYSIZE(fstypes) /* * When changing a partition, values are given as "tag=value", eg, * "start=10240 size=131072 type=4.2BSD". The V_ values are a * small-integer form of the keyword before the = in such pairs; the * VT_ values indicate the type of an associated datum. For example, * when writing "start=end-f size=10240 type=4.2BSD", the three * tag=value constructs correspnod to * * start=end-f V_START, VT_END, value 5 (partition f) * size=10240 V_SIZE, VT_NUM, value 10240 * type=4.2BSD V_TYPE, VT_TYPE, value the index of "4.2BSD" in * fstypes[] (7, at this writing). */ #define V_NONE (-1) #define V_START 0 #define V_SIZE 1 #define V_END 2 #define V_TYPE 3 #define V_FSIZE 4 #define V_BSIZE 5 #define V_CPG 6 #define V__N 7 #define VT_UNSET 0 /* not set, vals[] undefined */ #define VT_START 1 /* start-X, vals[] holds X */ #define VT_SIZE 2 /* size-X, vals[] holds X */ #define VT_END 3 /* end-X, vals[] holds X */ #define VT_TYPE 4 /* filesystem type, vals[] holds p_type */ #define VT_NUM 5 /* simple number, vals[] holds it */ #define VT_CHS 6 /* C/H/S value, vals[] holds equivalent sector number */ #define VTM(x) (1<2-byte integers with endiannesses * other than big or little, such as PDP-endian or weirder orders like * 1243. PDP-endian I don't care about (do any PDP BSDs use on-disk * disklabels?) and I have never even heard of a machine using * anything but big, little, or PDP endian, so I also don't care about * the really weird orders. (I have trouble imagining anyone building * such a machine except in a deliberate attempt to break assumptions * like this one.) */ static int native_endian(void) { static int e = END_GUESS; if (e == END_GUESS) { unsigned long int v; v = 0; *(char *)&v = 1; e = (v == 1) ? END_LITTLE : END_BIG; } return(e); } /* * Return the other endianness from this machine's native endianness. * We simply call native_endian() and convert LITTLE into BIG and vice * versa with a subtraction. See native_endian() for some relevant * considerations. */ static int swapped_endian(void) { return(END_LITTLE+END_BIG-native_endian()); } /* * Process the command-line arglist. Just step down it, handling each * arg in turn as a flag or non-flag. */ static void handleargs(int ac, char **av) { int skip; int errs; int argno; skip = 0; errs = 0; argno = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { switch (argno++) { case 0: setdisk(*av); break; default: fprintf(stderr,"%s: unrecognized argument `%s'\n",__progname,*av); errs ++; break; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-disk")) { WANTARG(); setdisk(av[skip]); continue; } if (!strcmp(*av,"-fixmagic")) { fixmagic = 1; continue; } if (!strcmp(*av,"-ro")) { readonly = 1; continue; } if (!strcmp(*av,"-fixsum")) { fixcksum = 1; continue; } if (!strcmp(*av,"-new")) { newlabel = 1; continue; } if (!strcmp(*av,"-q")) { quiet = 1; continue; } if (!strcmp(*av,"-endian")) { WANTARG(); if (!strcmp(av[skip],"guess")) endian = END_GUESS; else if (!strcmp(av[skip],"native")) endian = native_endian(); else if (!strcmp(av[skip],"swap")) endian = swapped_endian(); else if (!strcmp(av[skip],"big")) endian = END_BIG; else if (!strcmp(av[skip],"little")) endian = END_LITTLE; else { fprintf(stderr,"%s: unrecognized endianness keyword `%s'\n",__progname,av[skip]); fprintf(stderr,"%s: can be: guess native swap big little\n",__progname); errs ++; } continue; } if (!strcmp(*av,"-block")) { WANTARG(); label_block = strtol(av[skip],0,0); continue; } if (!strcmp(*av,"-offset")) { WANTARG(); label_offset = strtol(av[skip],0,0); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } /* * Extract a multi-byte value from labelbuf. This is used when * converting a label read off the disk into in-core form. nb is the * size of the value in bytes; at is the index of the first byte * within labelbuf. */ static unsigned long int labelget(int nb, int at) { const unsigned char *bp; unsigned long int v; int i; v = 0; switch (endian) { case END_BIG: bp = (const unsigned char *) &labelbuf[label_offset+at]; for (i=0;i>= 8; } break; case END_LITTLE: bp = (unsigned char *) &labelbuf[label_offset+at]; for (i=0;i>= 8; } break; } return(v); } /* * Figure out how many partitions a label has. This is basically the * npart value from the label struct, except we reduce it by dropping * trailing partitions that are start=0 size=0. */ static int label_npart(void) { int i; if (label.npart < MAXNPART) bzero(&label.parts[label.npart],(MAXNPART-label.npart)*sizeof(label.parts[0])); for (i=MAXNPART-1;i>=0;i--) if (label.parts[i].start || label.parts[i].size) break; return(i+1); } /* * The label area has been read off disk; unpack it into in-core form. * * This is where -new, -fixmagic, -fixsum, and endianness * guessing/checking are implemented. */ static const char *unpack_label(void) { int i; unsigned long int v; int bx; unsigned long int cksum; #define MINSIZE (74+sizeof(label.typename)+sizeof(label.packname)+(4*(ARRAYSIZE(label.drivedata)+ARRAYSIZE(label.spare)))) if (512-label_offset < MINSIZE) return("too small for minimal label"); maxnpart = (512 - label_offset - MINSIZE) / 16; if (maxnpart < 1) return("no space for any partitions"); if (newlabel) { label.type = 0; label.subtype = 0; bzero(&label.typename[0],sizeof(label.typename)); bzero(&label.packname[0],sizeof(label.packname)); label.secsize = 512; label.nsect = 0; label.ntrack = 0; label.ncyl = 0; label.spc = 0; label.spu = 0; label.spares_track = 0; label.spares_cyl = 0; label.acyl = 0; label.rpm = 0; label.interleave = 0; label.trackskew = 0; label.cylskew = 0; label.headswitch = 0; label.trkseek = 0; label.flags = 0; for (i=0;i 22) return("ridiculous partition count in label"); if (label.npart > maxnpart) return("label overflows sector"); label.bbsize = GET(4); label.sbsize = GET(4); for (i=0;i=0;i-=2) cksum ^= labelget(2,i); if (cksum) { if (fixcksum) { printf("note: ignoring bad label checksum\n"); } else { return("bad checksum"); } } return(0); #undef MINSIZE } /* * Read the label area off the disk and unpack it. Any failure * produces a printed error and exit. */ static void getlabel(void) { int rv; const char *lerr; if (lseek(diskfd,label_block*512ULL,L_SET) < 0) { fprintf(stderr,"%s: lseek to %qu on %s: %s\n",__progname,label_block*512ULL,diskname,strerror(errno)); exit(1); } rv = read(diskfd,&labelbuf[0],512); if (rv < 0) { fprintf(stderr,"%s: read label from %s: %s\n",__progname,diskname,strerror(errno)); exit(1); } if (rv != 512) { fprintf(stderr,"%s: short read from %s: wanted %d, got %d\n",__progname,diskname,512,rv); exit(1); } lerr = unpack_label(); if (lerr) { fprintf(stderr,"%s: bogus label on %s: %s\n",__progname,diskname,lerr); exit(1); } } /* * Pack the in-core label into labelbuf, preparatory to writing it to * the disk. */ static void pack_label(void) { int i; int bx; unsigned long int cksum; int ckx; bx = 0; #define PUT(n,v) ({ labelput(n,bx,v); bx += n; (void)0; }) PUT(4,LABEL_MAGIC); PUT(2,label.type); PUT(2,label.subtype); bcopy(&label.typename[0],&labelbuf[label_offset+bx],sizeof(label.typename)); bx += sizeof(label.typename); bcopy(&label.packname[0],&labelbuf[label_offset+bx],sizeof(label.packname)); bx += sizeof(label.packname); PUT(4,label.secsize); PUT(4,label.nsect); PUT(4,label.ntrack); PUT(4,label.ncyl); PUT(4,label.spc); PUT(4,label.spu); PUT(2,label.spares_track); PUT(2,label.spares_cyl); PUT(4,label.acyl); PUT(2,label.rpm); PUT(2,label.interleave); PUT(2,label.trackskew); PUT(2,label.cylskew); PUT(4,label.headswitch); PUT(4,label.trkseek); PUT(4,label.flags); for (i=0;i=0;i-=2) cksum ^= labelget(2,i); labelput(2,ckx,cksum); } /* * Conver the in-core label into the on-disk label and write it to the * disk. Any error produces a printed complaint and exit. */ static void putlabel(void) { int rv; if (lseek(diskfd,label_block*512ULL,L_SET) < 0) { fprintf(stderr,"%s: lseek to %qu on %s: %s\n",__progname,label_block*512ULL,diskname,strerror(errno)); exit(1); } rv = read(diskfd,&labelbuf[0],512); if (rv < 0) { fprintf(stderr,"%s: read label sector from %s: %s\n",__progname,diskname,strerror(errno)); exit(1); } if (rv != 512) { fprintf(stderr,"%s: short read from %s: wanted %d, got %d\n",__progname,diskname,512,rv); exit(1); } pack_label(); if (lseek(diskfd,label_block*512ULL,L_SET) < 0) { fprintf(stderr,"%s: lseek to %qu on %s: %s\n",__progname,label_block*512ULL,diskname,strerror(errno)); exit(1); } rv = write(diskfd,&labelbuf[0],512); if (rv < 0) { fprintf(stderr,"%s: write label to %s: %s\n",__progname,diskname,strerror(errno)); exit(1); } if (rv != 512) { fprintf(stderr,"%s: short write to %s: wanted %d, got %d\n",__progname,diskname,512,rv); exit(1); } label.dirty = 0; } /* * Initialize the taglen elements of fields[]. This is called whenever * we might need the taglen elements; it checks, doing nothing if it's * already run. */ static void set_taglens(void) { int i; if (fields[0].taglen < 1) { for (i=0;fields[i].tag;i++) fields[i].taglen = strlen(fields[i].tag); } } /* * Return the number of columns on the screen. This looks in various * places to try to find the column count, checking, in order of * priority, the terminal "window size" values, the termcap "co" * field, and a default of 80. It also uses a value of 20 if the * value otherwise determined is less than that, to avoid the display * algorithms stuttering too badly. */ static int screen_columns(void) { int ncols; #ifndef NO_TERMCAP_WIDTH char *term; char tbuf[1024]; #endif #if defined(TIOCGWINSZ) struct winsize wsz; #elif defined(TIOCGSIZE) struct ttysize tsz; #endif ncols = 80; #ifndef NO_TERMCAP_WIDTH term = getenv("TERM"); if (term && (tgetent(&tbuf[0],term) == 1)) { int n; n = tgetnum("co"); if (n > 1) ncols = n; } #endif #if defined(TIOCGWINSZ) if ((ioctl(1,TIOCGWINSZ,&wsz) == 0) && (wsz.ws_col > 0)) { ncols = wsz.ws_col; } #elif defined(TIOCGSIZE) if ((ioctl(1,TIOCGSIZE,&tsz) == 0) && (tsz.ts_cols > 0)) { ncols = tsz.ts_cols; } #endif if (ncols < 20) ncols = 20; return(ncols); } /* * Print function for unsigned 16-bit integers. * * Note that even though the field is only 16 bits wide, the value is * an "unsigned long int". This is because the *on-disk* value is 16 * bits; the *in-core* value is unsigned long. */ static int print_i16(FIELD *field, FILE *to) { fprintf(to,"%lu",*(unsigned long int *)field->loc); return(1); } /* * Value-change function for unsigned 16-bit integers. * * Note that even though the field is only 16 bits wide, the value is * an "unsigned long int". This is because it's the *on-disk* value * that's 16 bits; the *in-core* value is unsigned long. We check * that the value we're setting fits in 16 bits. */ static void chval_i16(const char *str, FIELD *f) { unsigned long int v; char *ep; v = strtoul(str,&ep,0); if ((ep == str) || (*ep && ((*ep != '\n') || ep[1])) || (v > 65535)) { fprintf(stderr,"invalid %s value %s\n",f->tag,str); return; } *(unsigned long int *)f->loc = v; } /* * Print function for unsigned 32-bit integers. * * Note that even though the field is only 32 bits wide, the value is * an "unsigned long int", which may well be wider than 32 bits. This * is because the *on-disk* value is 32 bits; the *in-core* value is * unsigned long. */ static int print_i32(FIELD *field, FILE *to) { fprintf(to,"%lu",*(unsigned long int *)field->loc); return(1); } /* * Value-change function for unsigned 32-bit integers. * * Note that even though the field is only 32 bits wide, the value is * an "unsigned long int", which may well be wider than 32 bits. This * is because it's the *on-disk* value that's 32 bits; the *in-core* * value is unsigned long. We check that the value we're setting fits * in 32 bits. */ static void chval_i32(const char *str, FIELD *f) { unsigned long int v; char *ep; v = strtoul(str,&ep,0); if ((ep == str) || (*ep && ((*ep != '\n') || ep[1])) || (v > 0xffffffff)) { fprintf(stderr,"invalid %s value %s\n",f->tag,str); return; } *(unsigned long int *)f->loc = v; } /* * Print function for max-16-octet strings. */ static int print_s16(FIELD *field, FILE *to) { fprintf(to,"%.16s",(const char *)field->loc); return(1); } /* * Value-change function for max-16-octet strings. */ static void chval_s16(const char *str, FIELD *f) { int l; l = strlen(str); if ((l > 0) && (str[l-1] == '\n')) l --; if (l > 16) { fprintf(stderr,"invalid %s value %s\n",f->tag,str); return; } bzero(f->loc,16); bcopy(str,f->loc,l); } /* * Print function for disk types (the "type" non-partition field). We * just print the string from dktypes[], or print it as a number if * it's out of range for the table we have. */ static int print_type(FIELD *field, FILE *to) { unsigned long int t; t = *(unsigned long int *)field->loc; if (t < NDKTYPES) fprintf(to,"%s",dktypes[t]); else fprintf(to,"%lu",t); return(1); } /* * Initialize the length values for dktypes[]. This is called whenever * we might need the lengths; it checks, doing nothing if it's already * run. */ static void setdktypelens(void) { int i; if (dktypelens[0] < 0) for (i=0;iloc = v; return; } setdktypelens(); l = strlen(str); if ((l > 0) && (str[l-1] == '\n')) l --; for (i=0;iloc = i; return; } } fprintf(stderr,"invalid %s value %s\n",f->tag,str); } /* * Match function for drivedata[]. This is dummied out, meaning it * will never match. It is, thus, impossible to change these values, * since it's impossible to name them. I've wanted to change them so * seldom this has never seemed worth the trouble of fixing; if anyone * has reason to care about them, obviously, it will need fixing. */ static int match_drdat(const char *str, int len) { str = str; len = len; return(0); } /* * Print function for drivedata[]. How this prints depends on the disk * type value, though most types just print them as an array of * numbers. */ static int print_drdat(FIELD *field, FILE *to) { unsigned long int *dd; int n; int i; dd = (unsigned long int *) field->loc; switch (label.type) { case DTYPE_SMD: fprintf(to,"smdflags: %lx",dd[0]); putc('\0',to); fprintf(to,"mindist: %lu",dd[1]); putc('\0',to); fprintf(to,"maxdist: %lu",dd[2]); putc('\0',to); fprintf(to,"sdist: %lu",dd[3]); putc('\0',to); n = 4; break; case DTYPE_ST506: fprintf(to,"precompcyl: %lu",dd[0]); putc('\0',to); fprintf(to,"gap3: %lu",dd[1]); putc('\0',to); n = 2; break; case DTYPE_SCSI: fprintf(to,"blind: %lu",dd[0]); putc('\0',to); n = 1; break; default: n = 0; break; } for (i=n;iloc; for (i=0;iloc; fprintf(to,"%lx=",f); for (i=0;d_flags_bits[i].name;i++) { if (i) putc(',',to); putc((f&d_flags_bits[i].bit)?'+':'-',to); fprintf(to,"%s",d_flags_bits[i].name); f &= ~d_flags_bits[i].bit; } if (f) fprintf(to,",%lx",f); return(1); } /* * Value-change function for the flags fields. We don't support * changing this, though we arguably should. */ static void chval_flags(const char *str, FIELD *f) { str=str; f=f; fprintf(stderr,"changing flags not yet implemented\n"); } /* * Print the non-partition fields from the label. Just loop through * fields[], printing each one in turn by calling its print function. */ static void print_label(void) { int i; int col; char *buf; int alloc; int fill; FILE *f; int maxcols; int wf(void *cookie __attribute__((__unused__)), const char *data, int len) { if (fill+len > alloc) buf = realloc(buf,alloc=fill+len); bcopy(data,buf+fill,len); fill += len; return(len); } void prepare_for(int w) { int s; s = col ? round_up(col+2,8)-col : 0; if (col && (col+s+w > maxcols)) { printf("\n"); col = 0; s = 0; } printf("%*s",s,""); col += s; } maxcols = screen_columns() - 2; set_taglens(); f = fwopen(0,wf); buf = 0; alloc = 0; fill = 0; col = 0; for (i=0;fields[i].tag;i++) { fill = 0; if ((*fields[i].print)(&fields[i],f)) { fflush(f); prepare_for(fields[i].taglen+2+fill); printf("%s: ",fields[i].tag); fwrite(buf,1,fill,stdout); col += fields[i].taglen + 2 + fill; } else { int b; int e; fflush(f); if ((fill == 0) || (buf[fill-1] != '\0')) { putc('\0',f); fflush(f); } fill --; b = 0; while (b < fill) { e = b + strlen(buf+b); if (e != b) { prepare_for(e-b); fwrite(buf+b,1,e-b,stdout); col += e - b; } b = e + 1; } } } if (col > 0) printf("\n"); } /* * Print a sector number as C/H/W values. Obviously, this works only * when nsect and ntrack are set, but that must be checked for * elsewhere; this function assumes it's not called unless it should * print. */ static void sprint_chs(char *s, unsigned long int v) { sprintf(s,"%lu/%lu/%lu", v/(label.nsect*label.ntrack), (v/label.nsect)%label.ntrack, v%label.nsect); } /* * The funopen() write function for the trailing whitespace stripper. * See the comment on the twss struct, above, for move. */ static int twss_write(void *tv, const char *buf, int len) { TWSS *t; int i; int n; t = tv; n = t->n; for (i=0;i0;n--) putc(' ',t->f); n = 0; putc(buf[i],t->f); break; } } t->n = n; return(len); } /* * The funopen() close function for the trailing whitespace stripper. * See the comment on the twss struct, above, for move. */ static int twss_close(void *tv) { free(tv); return(0); } /* * Wrap a FILE * in a trailing-whitepsace stripper, returning the * wrapper FILE *. */ static FILE *twss_open(FILE *to) { FILE *f; static TWSS *t = 0; if (t == 0) t = malloc(sizeof(TWSS)); if (t == 0) return(0); f = funopen(t,0,twss_write,0,twss_close); if (f == 0) return(0); t->f = to; t->n = 0; t = 0; return(f); } /* * Print the partition table in numeric form. The argument is true if * we want C/H/S values or false if we want big-array-of-sector * numbers (loosely, what IDE calls LBA - and SCSI has always used). */ static void print_part_numeric(int usechs) { int i; int j; int r; int w; unsigned long long int nbytes; int maxw[9]; FILE *f; char coltext[MAXPARTITIONS+1][9][64]; #define P(n) label.parts[n] #define B(n) P(n).start #define S(n) P(n).size #define E(n) P(n).end if (usechs && ((label.nsect == 0) || (label.ntrack == 0))) { printf("Can't use c/h/s form when nsect or ntrack is zero\n"); return; } coltext[0][0][0] = '\0'; strcpy(&coltext[0][1][0],"start"); strcpy(&coltext[0][2][0],"size"); strcpy(&coltext[0][3][0],"end"); strcpy(&coltext[0][4][0],"type"); strcpy(&coltext[0][5][0],"[fsize"); strcpy(&coltext[0][6][0],"bsize"); strcpy(&coltext[0][7][0],"cpg]"); strcpy(&coltext[0][8][0],""); r = 1; for (i=0;i maxw[i]) maxw[i] = w; } } f = twss_open(stdout); for (i=0;i 0); for (i=1;i edges[j])) { table[j][i] = 1; } else { table[j][i] = 0; } } } ncols = screen_columns() - 2; for (i=0;i ncols) { ce[n-1] = ncols; for (i=n-1;(i>0)&&(ce[i]<=ce[i-1]);i--) ce[i-1] = ce[i] - 1; if (ce[0] < 0) for (i=0;i 0) { r = -1; do { r ++; for (j=i-1;j>=0;j--) { if (row[j] != r) continue; for (k=0;k= 0); row[i] = r; } else { row[i] = -1; } } r = row[0]; for (i=1;i r) r = row[i]; line = malloc(ncols+1); for (i=0;i<=r;i++) { for (j=0;j=0)&&(line[j]==' ');j--) ; printf("%.*s\n",j+1,line); } free(line); #undef B #undef S #undef E } /* * Print the partition table. The argument is as for * print_part_numeric. */ static void print_part(int usechs) { print_part_numeric(usechs); printf("\n"); print_part_graphic(); } /* * Set the checksum in a struct disklabel. This is not really * S-command-specific; the reason it's #ifdef S_COMMAND is that the * only place it's used is in code that is S-command-specific. */ #ifdef S_COMMAND static void setcksum(const struct disklabel *lp, void *loc) { const unsigned char *start; const unsigned char *end; unsigned char sum0; unsigned char sum1; const unsigned char *p; start = (const void *) lp; end = (const void *) &lp->d_partitions[lp->d_npartitions]; if ((end-start) & 1) abort(); sum0 = 0; sum1 = 0; for (p=start;p=0;i--) { u.l.d_drivedata[i] = label.drivedata[i]; } for (i=min(ARRAYSIZE(label.spare),ARRAYSIZE(u.l.d_spare))-1;i>=0;i--) { u.l.d_spare[i] = label.spare[i]; } u.l.d_magic2 = LABEL_MAGIC; u.l.d_npartitions = label_npart(); u.l.d_bbsize = label.bbsize; u.l.d_sbsize = label.sbsize; for (i=0;i= maxnpart) || (pc != PARTLETTER(p))) return(0); pno = p; return(1); } if (vtm & VTM(TYPE)) { int i; int lmx; int lml; int lmn; int ml; if (*cp == '?') { printf("Known filesystem types:\n"); for (i=0;i lml) { lml = ml; lmx = i; lmn = 1; } else if (ml == lml) { lmn ++; } } if (cp[lml] && !ISSPACE(cp[lml])) { printf("Unrecognized filesystem type name\n"); return(1); } if (lmn != 1) { printf("Ambiguous filesystem type name\n"); return(1); } *epp = cp + lml; *vtp = VT_TYPE; *vp = lmx; return(0); } if ( (vtm & VTM(START)) && !strncmp(cp,"start-",6) && setpchar(cp[6]) ) { *epp = cp + 7; *vtp = VT_START; *vp = pno; return(0); } if ( (vtm & VTM(SIZE)) && !strncmp(cp,"size-",5) && setpchar(cp[5]) ) { *epp = cp + 6; *vtp = VT_SIZE; *vp = pno; return(0); } if ( (vtm & VTM(END)) && !strncmp(cp,"end-",4) && setpchar(cp[4]) ) { *epp = cp + 5; *vtp = VT_END; *vp = pno; return(0); } if (vtm & VTM(NUM)) { const char *ep; unsigned long int v; v = strtoul_cep(cp,&ep,0); if ((ep != cp) && (!*ep || ISSPACE(*ep))) { *epp = ep; *vtp = VT_NUM; *vp = v; return(0); } } if (vtm & VTM(CHS)) { const char *ep; unsigned long int c; unsigned long int h; unsigned long int s; c = strtoul_cep(cp,&ep,0); if ((ep != cp) && (*ep == '/')) { cp = ep + 1; h = strtoul_cep(cp,&ep,0); if (ep == cp) { printf("missing/invalid second number in c/h/s syntax"); return(1); } if (*ep != '/') { printf("missing slash after second number in c/h/s syntax"); return(1); } cp = ep + 1; s = strtoul_cep(cp,&ep,0); if (ep == cp) { printf("missing/invalid third number in c/h/s syntax"); return(1); } if (*ep && !ISSPACE(*ep)) { printf("junk after c/h/s syntax"); return(1); } if ((label.nsect == 0) || (label.ntrack == 0)) { printf("can't use c/h/s notation without nonzero nsectors and ntracks values\n"); return(1); } *epp = ep; *vtp = VT_CHS; *vp = (((c * label.ntrack) + h) * label.nsect) + s; return(0); } } printf("unrecognizable value\n"); return(1); } static void chpart(int pno, const char *rest) { const char *cp; const char *ep; int i; unsigned long int vals[7]; int valtype[7]; int setvals; PART newpart; for (cp=rest;*cp&&ISSPACE(*cp);cp++) ; if ((*cp == '\0') || (*cp == '?')) { printf("[a-%c] [ [ []]]\n",PARTLETTER(maxnpart-1)); printf(" arguments are such as would be valid values for the like-named\n"); printf(" keywords (see table below)\n"); printf("[a-%c] = [= ...] - set parameters\n",PARTLETTER(maxnpart-1)); printf(" (X is any valid partition letter)\n"); printf(" start start-X, end-X, or number\n"); printf(" size size-X or number\n"); printf(" end start-X, end-X, or number\n"); printf(" type type (use a type of ? for a list)\n"); printf(" fsize filesystem fragment size, for filesystems that use one\n"); printf(" bsize filesystem block size, for filesystems that use one\n"); printf(" cpg cylinders-per-group, for filesystems that use it\n"); printf("(When using the non-keyword form, can be end-X as well.)\n"); printf("Numbers for start, end, and size can be given as C/H/S\n"); printf("If two of start, size, and end are set, the third is recomputed automatically;\n"); printf("if all three are set, they must agree. If only one is set, one of the other\n"); printf("values is held constant and the third is recomputed. If size or end is set,\n"); printf("start is held constant; if start is set, size is held constant.\n"); return; } for (i=0;i= start\n"); return; } newpart.end = vals[V_END]; newpart.size = newpart.end - newpart.start; break; case (1<= start\n"); return; } newpart.start = vals[V_START]; newpart.end = vals[V_END]; newpart.size = newpart.end - newpart.start; break; case (1< vals[V_END]) { printf("note: partition clipped to beginning of disk\n"); vals[V_SIZE] = vals[V_END]; } newpart.size = vals[V_SIZE]; newpart.end = vals[V_END]; newpart.start = newpart.end - newpart.size; break; case (1<= label.npart) { label_npart(); label.npart = pno + 1; } label.parts[pno] = newpart; } static void docmd(void) { char cmdline[512]; if (! quiet) printf("bsdlabel> "); if (fgets(&cmdline[0],sizeof(cmdline),stdin) != &cmdline[0]) exit(0); switch (cmdline[0]) { case '?': printf("? - print this help\n"); printf("L - print label, except for partition table\n"); printf("B - print partition table, using block numbers\n"); printf("C - print partition table, using c/h/s\n"); printf("[a-%c] - change partition (=? for more info)\n",PARTLETTER(maxnpart-1)); printf("V - change a non-partition label value\n"); printf("V! - like V, but never recomputes any other values\n"); printf("W - write (possibly modified) label back out\n"); #ifdef S_COMMAND printf("S - set label in the kernel (orthogonal to W)\n"); #endif printf("Q - quit program (error if no write since last change)\n"); printf("Q! - quit program (unconditionally) [EOF also quits]\n"); break; case 'L': print_label(); break; case 'B': print_part(0); break; case 'C': print_part(1); break; case 'W': putlabel(); break; case 'Q': if ((cmdline[1] == '!') || !label.dirty) exit(0); printf("Label is dirty - use w to write it, or Q! to quit anyway.\n"); break; case 'S': #ifdef S_COMMAND setlabel(); #else printf("S not supported in this build\n"); #endif break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': chpart(LETTERPART(cmdline[0]),&cmdline[1]); break; case 'V': if (cmdline[1] == '!') chvalue(&cmdline[2],0); else chvalue(&cmdline[2],1); break; case '\n': break; default: printf("(Unrecognized command character %c ignored.)\n",cmdline[0]); break; } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); getlabel(); while (1) docmd(); } #if 0 /* XXX */ static __inline__ unsigned int how_many(unsigned int, unsigned int) __attribute__((__const__)); static __inline__ unsigned int how_many(unsigned int amt, unsigned int unit) { return((amt+unit-1)/unit); } static void set_endcyl(PART *p) { if (label.spc == 0) { p->endcyl = p->startcyl; } else { p->endcyl = p->startcyl + how_many(p->nblk,label.spc); } } static void skipspaces(const char **cpp) #define cp (*cpp) { while (*cp && isspace(*cp)) cp ++; } #undef cp static int scannum(const char **cpp, unsigned int *np, const char *tag) #define cp (*cpp) { unsigned int v; int nd; skipspaces(cpp); v = 0; nd = 0; while (*cp && isdigit(*cp)) { v = (10 * v) + (*cp++ - '0'); nd ++; } if (nd == 0) { printf("Missing/invalid %s: %s\n",tag,cp); return(0); } *np = v; return(1); } #undef cp static void chpart(int pno, const char *numbers) { unsigned int cyl0; unsigned int size; unsigned int sizec; unsigned int sizet; unsigned int sizes; skipspaces(&numbers); if (!bcmp(numbers,"end-",4) && numbers[4]) { int epno; epno = LETTERPART(numbers[4]); if ((epno >= 0) && (epno < NPART)) { cyl0 = label.partitions[epno].endcyl; numbers += 5; } else { if (! scannum(&numbers,&cyl0,"starting cylinder")) return; } } else if (!bcmp(numbers,"start-",6) && numbers[6]) { int spno; spno = LETTERPART(numbers[6]); if ((spno >= 0) && (spno < NPART)) { cyl0 = label.partitions[spno].startcyl; numbers += 7; } else { if (! scannum(&numbers,&cyl0,"starting cylinder")) return; } } else { if (! scannum(&numbers,&cyl0,"starting cylinder")) return; } skipspaces(&numbers); if (!bcmp(numbers,"end-",4) && numbers[4]) { int epno; epno = LETTERPART(numbers[4]); if ((epno >= 0) && (epno < NPART)) { if (label.partitions[epno].endcyl <= cyl0) { printf("Partition %c ends before cylinder %u\n",PARTLETTER(epno),cyl0); return; } size = label.partitions[epno].nblk; if (cyl0 > label.partitions[epno].startcyl) { size -= (cyl0 - label.partitions[epno].startcyl) * label.spc; } else if (cyl0 < label.partitions[epno].startcyl) { size += (label.partitions[epno].startcyl - cyl0) * label.spc; } numbers += 5; } else { if (! scannum(&numbers,&size,"partition size")) return; } } else if (!bcmp(numbers,"start-",6) && numbers[6]) { int spno; spno = LETTERPART(numbers[6]); if ((spno >= 0) && (spno < NPART)) { if (label.partitions[spno].startcyl <= cyl0) { printf("Partition %c starts before cylinder %u\n",PARTLETTER(spno),cyl0); return; } size = (label.partitions[spno].startcyl - cyl0) * label.spc; numbers += 7; } else { if (! scannum(&numbers,&size,"partition size")) return; } } else if (!bcmp(numbers,"size-",5) && numbers[5]) { int spno; spno = LETTERPART(numbers[5]); if ((spno >= 0) && (spno < NPART)) { size = label.partitions[spno].nblk; numbers += 6; } else { if (! scannum(&numbers,&size,"partition size")) return; } } else { if (! scannum(&numbers,&size,"partition size")) return; skipspaces(&numbers); if (*numbers == '/') { sizec = size; numbers ++; if (! scannum(&numbers,&sizet,"partition size track value")) return; skipspaces(&numbers); if (*numbers != '/') { printf("invalid c/t/s syntax - no second slash\n"); return; } numbers ++; if (! scannum(&numbers,&sizes,"partition size sector value")) return; size = sizes + (label.nsect * (sizet + (label.nhead * sizec))); } } if (label.spc && (size % label.spc)) { printf("Warning: size is not a multiple of cylinder size (is %u/%u/%u)\n",size/label.spc,(size%label.spc)/label.nsect,size%label.nsect); } label.partitions[pno].startcyl = cyl0; label.partitions[pno].nblk = size; set_endcyl(&label.partitions[pno]); if ( (label.partitions[pno].startcyl*label.spc)+label.partitions[pno].nblk > label.spc*label.ncyl ) { printf("Warning: partition extends beyond end of disk\n"); } label.dirty = 1; } static void chval_ascii(const char *cp, FIELD *f) { const char *nl; skipspaces(&cp); nl = index(cp,'\n'); if (nl == 0) nl = cp + strlen(cp); if (nl-cp > 128) { printf("ascii label string too long - max 128 characters\n"); } else { bzero(f->loc,128); bcopy(cp,f->loc,nl-cp); label.dirty = 1; } } static void chval_int(const char *cp, FIELD *f) { int v; if (! scannum(&cp,&v,"value")) return; *(unsigned int *)f->loc = v; label.dirty = 1; } static void chvalue(const char *str) { const char *cp; int n; int i; if (fields[0].taglen < 1) { for (i=0;fields[i].tag;i++) fields[i].taglen = strlen(fields[i].tag); } skipspaces(&str); cp = str; while (*cp && !isspace(*cp)) cp ++; n = cp - str; for (i=0;fields[i].tag;i++) { if ((n == fields[i].taglen) && !bcmp(str,fields[i].tag,n)) { (*fields[i].chval)(cp,&fields[i]); if (fields[i].changed) (*fields[i].changed)(); break; } } if (! fields[i].tag) { printf("bad name %.*s - see l output for names\n",n,str); } } static void update_spc(void) { int i; label.spc = label.nhead * label.nsect; for (i=0;i