2 * XXX replace all the checks on object validity with
3 * calls to valid<object>
6 * Copyright (c) 1997, 1998, 1999
7 * Nan Yang Computer Services Limited. All rights reserved.
9 * Parts copyright (c) 1997, 1998 Cybernet Corporation, NetMAX project.
11 * Written by Greg Lehey
13 * This software is distributed under the so-called ``Berkeley
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 * 3. All advertising materials mentioning features or use of this software
25 * must display the following acknowledgement:
26 * This product includes software developed by Nan Yang Computer
28 * 4. Neither the name of the Company nor the names of its contributors
29 * may be used to endorse or promote products derived from this software
30 * without specific prior written permission.
32 * This software is provided ``as is'', and any express or implied
33 * warranties, including, but not limited to, the implied warranties of
34 * merchantability and fitness for a particular purpose are disclaimed.
35 * In no event shall the company or contributors be liable for any
36 * direct, indirect, incidental, special, exemplary, or consequential
37 * damages (including, but not limited to, procurement of substitute
38 * goods or services; loss of use, data, or profits; or business
39 * interruption) however caused and on any theory of liability, whether
40 * in contract, strict liability, or tort (including negligence or
41 * otherwise) arising in any way out of the use of this software, even if
42 * advised of the possibility of such damage.
44 * $Id: vinumioctl.c,v 1.14 2000/10/27 03:07:53 grog Exp grog $
48 #include <dev/vinum/vinumhdr.h>
49 #include <dev/vinum/request.h>
52 #include <sys/reboot.h>
55 void attachobject(struct vinum_ioctl_msg *);
56 void detachobject(struct vinum_ioctl_msg *);
57 void renameobject(struct vinum_rename_msg *);
58 void replaceobject(struct vinum_ioctl_msg *);
59 void moveobject(struct vinum_ioctl_msg *);
61 jmp_buf command_fail; /* return on a failed command */
76 unsigned int index; /* for transferring config info */
77 unsigned int sdno; /* for transferring config info */
78 int fe; /* free list element number */
79 struct _ioctl_reply *ioctl_reply = (struct _ioctl_reply *) data; /* struct to return */
81 /* First, decide what we're looking at */
82 switch (DEVTYPE(dev)) {
83 case VINUM_SUPERDEV_TYPE: /* ordinary super device */
84 ioctl_reply = (struct _ioctl_reply *) data; /* save the address to reply to */
88 if (((struct debuginfo *) data)->changeit) /* change debug settings */
89 debug = (((struct debuginfo *) data)->param);
91 if (debug & DEBUG_REMOTEGDB)
92 boothowto |= RB_GDB; /* serial debug line */
94 boothowto &= ~RB_GDB; /* local ddb */
95 Debugger("vinum debug");
97 ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */
98 ioctl_reply->error = 0;
102 case VINUM_CREATE: /* create a vinum object */
103 error = lock_config(); /* get the config for us alone */
104 if (error) /* can't do it, */
105 return error; /* give up */
106 error = setjmp(command_fail); /* come back here on error */
107 if (error == 0) /* first time, */
108 ioctl_reply->error = parse_user_config((char *) data, /* update the config */
110 else if (ioctl_reply->error == 0) { /* longjmp, but no error status */
111 ioctl_reply->error = EINVAL; /* note that something's up */
112 ioctl_reply->msg[0] = '\0'; /* no message? */
115 return 0; /* must be 0 to return the real error info */
117 case VINUM_GETCONFIG: /* get the configuration information */
118 bcopy(&vinum_conf, data, sizeof(vinum_conf));
121 /* start configuring the subsystem */
122 case VINUM_STARTCONFIG:
123 return start_config(*(int *) data); /* just lock it. Parameter is 'force' */
126 * Move the individual parts of the config to user space.
128 * Specify the index of the object in the first word of data,
129 * and return the object there
131 case VINUM_DRIVECONFIG:
132 index = *(int *) data; /* get the index */
133 if (index >= (unsigned) vinum_conf.drives_allocated) /* can't do it */
134 return ENXIO; /* bang */
135 bcopy(&DRIVE[index], data, sizeof(struct _drive)); /* copy the config item out */
139 index = *(int *) data; /* get the index */
140 if (index >= (unsigned) vinum_conf.subdisks_allocated) /* can't do it */
141 return ENXIO; /* bang */
142 bcopy(&SD[index], data, sizeof(struct _sd)); /* copy the config item out */
145 case VINUM_PLEXCONFIG:
146 index = *(int *) data; /* get the index */
147 if (index >= (unsigned) vinum_conf.plexes_allocated) /* can't do it */
148 return ENXIO; /* bang */
149 bcopy(&PLEX[index], data, sizeof(struct _plex)); /* copy the config item out */
152 case VINUM_VOLCONFIG:
153 index = *(int *) data; /* get the index */
154 if (index >= (unsigned) vinum_conf.volumes_allocated) /* can't do it */
155 return ENXIO; /* bang */
156 bcopy(&VOL[index], data, sizeof(struct _volume)); /* copy the config item out */
159 case VINUM_PLEXSDCONFIG:
160 index = *(int *) data; /* get the plex index */
161 sdno = ((int *) data)[1]; /* and the sd index */
162 if ((index >= (unsigned) vinum_conf.plexes_allocated) /* plex doesn't exist */
163 ||(sdno >= PLEX[index].subdisks)) /* or it doesn't have this many subdisks */
164 return ENXIO; /* bang */
165 bcopy(&SD[PLEX[index].sdnos[sdno]], /* copy the config item out */
171 * We get called in two places: one from the
172 * userland config routines, which call us
173 * to complete the config and save it. This
174 * call supplies the value 0 as a parameter.
176 * The other place is from the user "saveconfig"
177 * routine, which can only work if we're *not*
178 * configuring. In this case, supply parameter 1.
180 case VINUM_SAVECONFIG:
181 if (VFLAGS & VF_CONFIGURING) { /* must be us, the others are asleep */
182 if (*(int *) data == 0) /* finish config */
183 finish_config(1); /* finish the configuration and update it */
185 return EBUSY; /* can't do it now */
187 save_config(); /* save configuration to disk */
190 case VINUM_RELEASECONFIG: /* release the config */
191 if (VFLAGS & VF_CONFIGURING) { /* must be us, the others are asleep */
192 finish_config(0); /* finish the configuration, don't change it */
193 save_config(); /* save configuration to disk */
195 error = EINVAL; /* release what config? */
199 ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */
200 ioctl_reply->error = 0;
203 case VINUM_RESETCONFIG:
204 if (vinum_inactive(0)) { /* if the volumes are not active */
206 * Note the open count. We may be called from v, so we'll be open.
207 * Keep the count so we don't underflow
209 free_vinum(1); /* clean up everything */
210 log(LOG_NOTICE, "vinum: CONFIGURATION OBLITERATED\n");
211 ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */
212 ioctl_reply->error = 0;
218 setstate((struct vinum_ioctl_msg *) data); /* set an object state */
222 * Set state by force, without changing
225 case VINUM_SETSTATE_FORCE:
226 setstate_by_force((struct vinum_ioctl_msg *) data); /* set an object state */
234 case VINUM_MALLOCINFO:
235 return vinum_mallocinfo(data);
238 return vinum_rqinfo(data);
241 case VINUM_LABEL: /* label a volume */
242 ioctl_reply->error = write_volume_label(*(int *) data); /* index of the volume to label */
243 ioctl_reply->msg[0] = '\0'; /* no message */
247 remove((struct vinum_ioctl_msg *) data); /* remove an object */
250 case VINUM_GETFREELIST: /* get a drive free list element */
251 index = *(int *) data; /* get the drive index */
252 fe = ((int *) data)[1]; /* and the free list element */
253 if ((index >= (unsigned) vinum_conf.drives_allocated) /* plex doesn't exist */
254 ||(DRIVE[index].state == drive_unallocated))
256 if (fe >= DRIVE[index].freelist_entries) /* no such entry */
258 bcopy(&DRIVE[index].freelist[fe],
260 sizeof(struct drive_freelist));
263 case VINUM_RESETSTATS:
264 resetstats((struct vinum_ioctl_msg *) data); /* reset object stats */
267 /* attach an object to a superordinate object */
269 attachobject((struct vinum_ioctl_msg *) data);
272 /* detach an object from a superordinate object */
274 detachobject((struct vinum_ioctl_msg *) data);
277 /* rename an object */
279 renameobject((struct vinum_rename_msg *) data);
282 /* replace an object */
284 replaceobject((struct vinum_ioctl_msg *) data);
288 vinum_daemon(); /* perform the daemon */
291 case VINUM_FINDDAEMON: /* check for presence of daemon */
292 return vinum_finddaemon();
295 case VINUM_SETDAEMON: /* set daemon flags */
296 return vinum_setdaemonopts(*(int *) data);
298 case VINUM_GETDAEMON: /* get daemon flags */
299 *(int *) data = daemon_options;
302 case VINUM_PARITYOP: /* check/rebuild RAID-4/5 parity */
303 parityops((struct vinum_ioctl_msg *) data);
308 moveobject((struct vinum_ioctl_msg *) data);
315 case VINUM_DRIVE_TYPE:
318 "vinumioctl: invalid ioctl from process %d (%s): %lx\n",
319 curthread->td_proc->p_pid,
320 curthread->td_proc->p_comm,
325 case VINUM_RAWSD_TYPE:
331 case DIOCGDINFO: /* get disk label */
332 get_volume_label(sd->name, 1, sd->sectors, (struct disklabel *) data);
336 * We don't have this stuff on hardware,
337 * so just pretend to do it so that
338 * utilities don't get upset.
340 case DIOCWDINFO: /* write partition info */
341 case DIOCSDINFO: /* set partition info */
342 return 0; /* not a titty */
345 return ENOTTY; /* not my kind of ioctl */
348 return 0; /* pretend we did it */
350 case VINUM_RAWPLEX_TYPE:
351 case VINUM_PLEX_TYPE:
357 case DIOCGDINFO: /* get disk label */
358 get_volume_label(plex->name, 1, plex->length, (struct disklabel *) data);
362 * We don't have this stuff on hardware,
363 * so just pretend to do it so that
364 * utilities don't get upset.
366 case DIOCWDINFO: /* write partition info */
367 case DIOCSDINFO: /* set partition info */
368 return 0; /* not a titty */
371 return ENOTTY; /* not my kind of ioctl */
374 return 0; /* pretend we did it */
376 case VINUM_VOLUME_TYPE:
379 if ((unsigned) objno >= (unsigned) vinum_conf.volumes_allocated) /* not a valid volume */
382 if (vol->state != volume_up) /* not up, */
383 return EIO; /* I/O error */
386 case DIOCGDINFO: /* get disk label */
387 get_volume_label(vol->name, vol->plexes, vol->size, (struct disklabel *) data);
391 * We don't have this stuff on hardware,
392 * so just pretend to do it so that
393 * utilities don't get upset.
395 case DIOCWDINFO: /* write partition info */
396 case DIOCSDINFO: /* set partition info */
397 return 0; /* not a titty */
399 case DIOCWLABEL: /* set or reset label writeable */
400 if ((flag & FWRITE) == 0) /* not writeable? */
401 return EACCES; /* no, die */
402 if (*(int *) data != 0) /* set it? */
403 vol->flags |= VF_WLABEL; /* yes */
405 vol->flags &= ~VF_WLABEL; /* no, reset */
409 return ENOTTY; /* not my kind of ioctl */
417 * The following four functions check the supplied
418 * object index and return a pointer to the object
419 * if it exists. Otherwise they longjump out via
423 validdrive(int driveno, struct _ioctl_reply *reply)
425 if ((driveno < vinum_conf.drives_allocated)
426 && (DRIVE[driveno].state > drive_referenced))
427 return &DRIVE[driveno];
428 strcpy(reply->msg, "No such drive");
429 reply->error = ENOENT;
434 validsd(int sdno, struct _ioctl_reply *reply)
436 if ((sdno < vinum_conf.subdisks_allocated)
437 && (SD[sdno].state > sd_referenced))
439 strcpy(reply->msg, "No such subdisk");
440 reply->error = ENOENT;
445 validplex(int plexno, struct _ioctl_reply *reply)
447 if ((plexno < vinum_conf.plexes_allocated)
448 && (PLEX[plexno].state > plex_referenced))
449 return &PLEX[plexno];
450 strcpy(reply->msg, "No such plex");
451 reply->error = ENOENT;
456 validvol(int volno, struct _ioctl_reply *reply)
458 if ((volno < vinum_conf.volumes_allocated)
459 && (VOL[volno].state > volume_uninit))
461 strcpy(reply->msg, "No such volume");
462 reply->error = ENOENT;
466 /* reset an object's stats */
468 resetstats(struct vinum_ioctl_msg *msg)
470 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
474 if (msg->index < vinum_conf.drives_allocated) {
475 struct drive *drive = &DRIVE[msg->index];
476 if (drive->state > drive_referenced) {
477 drive->reads = 0; /* number of reads on this drive */
478 drive->writes = 0; /* number of writes on this drive */
479 drive->bytes_read = 0; /* number of bytes read */
480 drive->bytes_written = 0; /* number of bytes written */
484 reply->error = EINVAL;
488 if (msg->index < vinum_conf.subdisks_allocated) {
489 struct sd *sd = &SD[msg->index];
490 if (sd->state > sd_referenced) {
491 sd->reads = 0; /* number of reads on this subdisk */
492 sd->writes = 0; /* number of writes on this subdisk */
493 sd->bytes_read = 0; /* number of bytes read */
494 sd->bytes_written = 0; /* number of bytes written */
498 reply->error = EINVAL;
504 if (msg->index < vinum_conf.plexes_allocated) {
505 struct plex *plex = &PLEX[msg->index];
506 if (plex->state > plex_referenced) {
508 plex->writes = 0; /* number of writes on this plex */
509 plex->bytes_read = 0; /* number of bytes read */
510 plex->bytes_written = 0; /* number of bytes written */
511 plex->recovered_reads = 0; /* number of recovered read operations */
512 plex->degraded_writes = 0; /* number of degraded writes */
513 plex->parityless_writes = 0; /* number of parityless writes */
514 plex->multiblock = 0; /* requests that needed more than one block */
515 plex->multistripe = 0; /* requests that needed more than one stripe */
519 reply->error = EINVAL;
525 if (msg->index < vinum_conf.volumes_allocated) {
526 struct volume *vol = &VOL[msg->index];
527 if (vol->state > volume_uninit) {
528 vol->bytes_read = 0; /* number of bytes read */
529 vol->bytes_written = 0; /* number of bytes written */
530 vol->reads = 0; /* number of reads on this volume */
531 vol->writes = 0; /* number of writes on this volume */
532 vol->recovered_reads = 0; /* reads recovered from another plex */
536 reply->error = EINVAL;
539 case invalid_object: /* can't get this */
540 reply->error = EINVAL;
545 /* attach an object to a superior object */
547 attachobject(struct vinum_ioctl_msg *msg)
549 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
556 case drive_object: /* you can't attach a drive to anything */
557 case volume_object: /* nor a volume */
558 case invalid_object: /* "this can't happen" */
559 reply->error = EINVAL;
560 reply->msg[0] = '\0'; /* vinum(8) doesn't do this */
564 sd = validsd(msg->index, reply);
565 if (sd == NULL) /* not a valid subdisk */
567 plex = validplex(msg->otherobject, reply);
570 * We should be more intelligent about this.
571 * We should be able to reattach a dead
572 * subdisk, but if we want to increase the total
573 * number of subdisks, we have a lot of reshuffling
576 if ((plex->organization != plex_concat) /* can't attach to striped and RAID-4/5 */
577 &&(!msg->force)) { /* without using force */
578 reply->error = EINVAL; /* no message, the user should check */
579 strcpy(reply->msg, "Can't attach to this plex organization");
582 if (sd->plexno >= 0) { /* already belong to a plex */
583 reply->error = EBUSY; /* no message, the user should check */
584 reply->msg[0] = '\0';
587 sd->plexoffset = msg->offset; /* this is where we want it */
588 set_sd_state(sd->sdno, sd_stale, setstate_force); /* make sure it's stale */
589 give_sd_to_plex(plex->plexno, sd->sdno); /* and give it to the plex */
590 update_sd_config(sd->sdno, 0);
593 if (sd->state == sd_reviving)
594 reply->error = EAGAIN; /* need to revive it */
600 plex = validplex(msg->index, reply); /* get plex */
603 vol = validvol(msg->otherobject, reply); /* and volume information */
605 if ((vol->plexes == MAXPLEX) /* we have too many already */
606 ||(plex->volno >= 0)) { /* or the plex has an owner */
607 reply->error = EINVAL; /* no message, the user should check */
608 reply->msg[0] = '\0';
611 for (sdno = 0; sdno < plex->subdisks; sdno++) {
612 sd = &SD[plex->sdnos[sdno]];
614 if (sd->state > sd_down) /* real subdisk, vaguely accessible */
615 set_sd_state(plex->sdnos[sdno], sd_stale, setstate_force); /* make it stale */
617 set_plex_state(plex->plexno, plex_up, setstate_none); /* update plex state */
618 give_plex_to_volume(msg->otherobject, msg->index); /* and give it to the volume */
619 update_plex_config(plex->plexno, 0);
621 reply->error = 0; /* all went well */
626 /* detach an object from a superior object */
628 detachobject(struct vinum_ioctl_msg *msg)
630 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
638 case drive_object: /* you can't detach a drive from anything */
639 case volume_object: /* nor a volume */
640 case invalid_object: /* "this can't happen" */
641 reply->error = EINVAL;
642 reply->msg[0] = '\0'; /* vinum(8) doesn't do this */
646 sd = validsd(msg->index, reply);
649 if (sd->plexno < 0) { /* doesn't belong to a plex */
650 reply->error = ENOENT;
651 strcpy(reply->msg, "Subdisk is not attached");
653 } else { /* valid plex number */
654 plex = &PLEX[sd->plexno];
655 if ((!msg->force) /* don't force things */
656 &&((plex->state == plex_up) /* and the plex is up */
657 ||((plex->state == plex_flaky) && sd->state == sd_up))) { /* or flaky with this sd up */
658 reply->error = EBUSY; /* we need this sd */
659 reply->msg[0] = '\0';
662 sd->plexno = -1; /* anonymous sd */
663 if (plex->subdisks == 1) { /* this was the only subdisk */
664 Free(plex->sdnos); /* free the subdisk array */
665 plex->sdnos = NULL; /* and note the fact */
666 plex->subdisks_allocated = 0; /* no subdisk space */
668 for (sdno = 0; sdno < plex->subdisks; sdno++) {
669 if (plex->sdnos[sdno] == msg->index) /* found our subdisk */
672 if (sdno < (plex->subdisks - 1)) /* not the last one, compact */
673 bcopy(&plex->sdnos[sdno + 1],
675 (plex->subdisks - 1 - sdno) * sizeof(int));
678 if (!bcmp(plex->name, sd->name, strlen(plex->name) + 1))
679 /* this subdisk is named after the plex */
683 min(strlen(sd->name) + 1, MAXSDNAME - 3));
684 bcopy("ex-", sd->name, 3);
685 sd->name[MAXSDNAME - 1] = '\0';
687 update_plex_config(plex->plexno, 0);
688 if (isstriped(plex)) /* we've just mutilated our plex, */
689 set_plex_state(plex->plexno,
691 setstate_force | setstate_configuring);
698 plex = validplex(msg->index, reply); /* get plex */
701 if (plex->volno >= 0) {
702 int volno = plex->volno;
705 if ((!msg->force) /* don't force things */
706 &&((vol->state == volume_up) /* and the volume is up */
707 &&(vol->plexes == 1))) { /* and this is the last plex */
709 * XXX As elsewhere, check whether we will lose
710 * mapping by removing this plex
712 reply->error = EBUSY; /* we need this plex */
713 reply->msg[0] = '\0';
716 plex->volno = -1; /* anonymous plex */
717 for (plexno = 0; plexno < vol->plexes; plexno++) {
718 if (vol->plex[plexno] == msg->index) /* found our plex */
721 if (plexno < (vol->plexes - 1)) /* not the last one, compact */
722 bcopy(&vol->plex[plexno + 1],
724 (vol->plexes - 1 - plexno) * sizeof(int));
726 vol->last_plex_read = 0; /* don't go beyond the end */
727 if (!bcmp(vol->name, plex->name, strlen(vol->name) + 1))
728 /* this plex is named after the volume */
730 /* First, check if the subdisks are the same */
734 for (sdno = 0; sdno < plex->subdisks; sdno++) {
735 struct sd *sd = &SD[plex->sdnos[sdno]];
737 if (!bcmp(plex->name, sd->name, strlen(plex->name) + 1))
738 /* subdisk is named after the plex */
742 min(strlen(sd->name) + 1, MAXSDNAME - 3));
743 bcopy("ex-", sd->name, 3);
744 sd->name[MAXSDNAME - 1] = '\0';
750 min(strlen(plex->name) + 1, MAXPLEXNAME - 3));
751 bcopy("ex-", plex->name, 3);
752 plex->name[MAXPLEXNAME - 1] = '\0';
754 update_volume_config(volno, 0);
758 reply->error = ENOENT;
759 strcpy(reply->msg, "Plex is not attached");
765 renameobject(struct vinum_rename_msg *msg)
767 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
774 case drive_object: /* you can't attach a drive to anything */
775 if (find_drive(msg->newname, 0) >= 0) { /* we have that name already, */
776 reply->error = EEXIST;
777 reply->msg[0] = '\0';
780 drive = validdrive(msg->index, reply);
782 bcopy(msg->newname, drive->label.name, MAXDRIVENAME);
788 case sd_object: /* you can't attach a subdisk to anything */
789 if (find_subdisk(msg->newname, 0) >= 0) { /* we have that name already, */
790 reply->error = EEXIST;
791 reply->msg[0] = '\0';
794 sd = validsd(msg->index, reply);
796 bcopy(msg->newname, sd->name, MAXSDNAME);
797 update_sd_config(sd->sdno, 0);
803 case plex_object: /* you can't attach a plex to anything */
804 if (find_plex(msg->newname, 0) >= 0) { /* we have that name already, */
805 reply->error = EEXIST;
806 reply->msg[0] = '\0';
809 plex = validplex(msg->index, reply);
811 bcopy(msg->newname, plex->name, MAXPLEXNAME);
812 update_plex_config(plex->plexno, 0);
818 case volume_object: /* you can't attach a volume to anything */
819 if (find_volume(msg->newname, 0) >= 0) { /* we have that name already, */
820 reply->error = EEXIST;
821 reply->msg[0] = '\0';
824 vol = validvol(msg->index, reply);
826 bcopy(msg->newname, vol->name, MAXVOLNAME);
827 update_volume_config(msg->index, 0);
834 reply->error = EINVAL;
835 reply->msg[0] = '\0';
840 * Replace one object with another.
841 * Currently only for drives.
842 * message->index is the drive number of the old drive
843 * message->otherobject is the drive number of the new drive
846 replaceobject(struct vinum_ioctl_msg *msg)
848 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
850 reply->error = ENODEV; /* until I know how to do this */
851 strcpy(reply->msg, "replace not implemented yet");
852 /* save_config (); */
856 moveobject(struct vinum_ioctl_msg *msg)
858 struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
862 /* Check that our objects are valid (i.e. they exist) */
863 drive = validdrive(msg->index, (struct _ioctl_reply *) msg);
866 sd = validsd(msg->otherobject, (struct _ioctl_reply *) msg);
869 if (sd->driveno == msg->index) /* sd already belongs to drive */
872 if (sd->state > sd_stale)
873 set_sd_state(sd->sdno, sd_stale, setstate_force); /* make the subdisk stale */
875 sd->state = sd_empty;
876 if (sd->plexno >= 0) /* part of a plex, */
877 update_plex_state(sd->plexno); /* update its state */
879 /* Return the space on the old drive */
880 if ((sd->driveno >= 0) /* we have a drive, */
881 &&(sd->sectors > 0)) /* and some space on it */
882 return_drive_space(sd->driveno, /* return the space */
886 /* Reassign the old subdisk */
887 sd->driveno = msg->index;
888 sd->driveoffset = -1; /* let the drive decide where to put us */
889 give_sd_to_drive(sd->sdno);
893 /* Local Variables: */
894 /* fill-column: 50 */