From 73671a2f4f0e6e03a509ff5a97ab70d7b0db12b1 Mon Sep 17 00:00:00 2001 From: kraxel Date: Wed, 9 Feb 2005 11:34:37 +0000 Subject: - add files to repository. --- INSTALL | 34 ++ Makefile | 62 ++ README | 184 ++++++ RegEdit.c | 1795 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ RegEdit.h | 65 +++ RegEditI.h | 368 ++++++++++++ autojuke.c | 344 +++++++++++ autojuke.conf | 8 + autojuke.man | 60 ++ chio.h | 160 +++++ list.h | 166 ++++++ man.c | 116 ++++ man.h | 4 + mover.c | 367 ++++++++++++ mover.man | 105 ++++ unload.c | 65 +++ unload.man | 27 + xmover.c | 777 +++++++++++++++++++++++++ xmover.h | 125 ++++ xmover.man | 53 ++ 20 files changed, 4885 insertions(+) create mode 100644 INSTALL create mode 100644 README create mode 100644 RegEdit.c create mode 100644 RegEdit.h create mode 100644 RegEditI.h create mode 100644 autojuke.c create mode 100644 autojuke.conf create mode 100644 autojuke.man create mode 100644 chio.h create mode 100644 list.h create mode 100644 man.c create mode 100644 man.h create mode 100644 mover.c create mode 100644 mover.man create mode 100644 unload.c create mode 100644 unload.man create mode 100644 xmover.c create mode 100644 xmover.h create mode 100644 xmover.man diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..a7ee64b --- /dev/null +++ b/INSTALL @@ -0,0 +1,34 @@ + +Compiling the package +--------------------- + +The driver itself is provided as patch. There are patches for both +2.2.x and 2.4.x kernels. Apply the patch, configure the kernel, +recompile. The 2.2.x version isn't maintained any more. The old patch +is still there, but the latest and greatest stuff is available for 2.4.x +only. + +A simple 'make' should compile the tools. After compiling you should +have the following files: + + mover - a small program for testing the driver / sending commands + to the changer + xmover - a X11 frontend for the driver -- needs Motif. + autojuke - a tool for using a changer with autofs. + unload - a even smaller program to send a eject to any SCSI device + load - (symlink to unload) sends a load instead of eject. + +man-pages for these utilities are available. Also have a look at the +README for some general informations about scsi changers and about the +driver. + +In the todo subdirectory is autojuke version with support for +double-sided media hacked in. Drawback is that volume tag support is +broken in this version. To be merged some day ... + +Have fun, + + Gerd + +-- +Gerd Knorr diff --git a/Makefile b/Makefile index e69de29..707a0c8 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,62 @@ +DESTDIR = +prefix = /usr/local + +etc = $(DESTDIR)/etc +bindir = $(DESTDIR)$(prefix)/bin +sbindir = $(DESTDIR)$(prefix)/sbin +mandir = $(DESTDIR)$(prefix)/share/man + +RELEASE := $(shell uname -r) +KINC := /lib/modules/$(RELEASE)/build/include +#KINC := /usr/src/linux/include + +XINC := /usr/X11R6/include +XLIB := /usr/X11R6/lib + +CC := gcc +OPTFLAGS:= -O2 +CFLAGS := -Wall -g $(OPTFLAGS) +#CFLAGS += -I$(KINC) +CFLAGS += -I$(XINC) -L$(XLIB) + +VERSION := 0.20 +PROGS := mover autojuke + +# poor man's autoconf :-) +MOTIF := $(shell test -d $(XINC)/Xm && echo "yes") +ifeq ($(MOTIF),yes) +PROGS += xmover +endif + +% : %.c + $(CC) $(CFLAGS) -o $@ $< + +all: $(PROGS) + +xmover: xmover.o man.o RegEdit.o + $(CC) $(CFLAGS) -o $@ $^ -lXm -lXt -lX11 + +xmover.o: xmover.c xmover.h + +xmover.h: xmover.ad + perl fallback.pl < $< > $@ + +load: unload + ln -s unload load + +clean: + rm -f *.o *~ $(PROGS) load + +install: $(PROGS) + mkdir -p $(etc) $(bindir) $(sbindir) + mkdir -p $(mandir)/man1 $(mandir)/man8 + install -s -m755 mover $(bindir) + install -m644 mover.man $(mandir)/man1/mover.1 + install -s -m755 autojuke $(sbindir) + install -m644 autojuke.man $(mandir)/man8/autojuke.8 + install -m644 autojuke.conf $(etc) +ifeq ($(MOTIF),yes) + install -s -m755 xmover $(bindir) + install -m644 xmover.man $(mandir)/man1/xmover.1 +endif + diff --git a/README b/README new file mode 100644 index 0000000..04a4ea9 --- /dev/null +++ b/README @@ -0,0 +1,184 @@ + +README for the SCSI media changer driver +======================================== + +This is a driver for SCSI Medium Changer devices, which are listed +with "Type: Medium Changer" in /proc/scsi/scsi. + +This is for *real* Jukeboxes. It is *not* supported to work with +common small CD-ROM changers, neither one-lun-per-slot SCSI changers +nor IDE drives. + +Userland tools available from: http://bytesex.org/changer.html + + +General Information +------------------- + +First some words about how changers work: A changer has 2 (possibly +more) SCSI ID's. One for the changer device which controls the robot, +and one for the device which actually reads and writes the data. The +later may be anything, a MOD, a CD-ROM, a tape or whatever. For the +changer device this is a "don't care", he *only* shuffles around the +media, nothing else. + + +The SCSI changer model is complex, compared to - for example - IDE-CD +changers. But it allows to handle nearly all possible cases. It knows +4 different types of changer elements: + + media transport - this one shuffles around the media, i.e. the + transport arm. Also known as "picker". + storage - a slot which can hold a media. + import/export - the same as above, but is accessable from outside, + i.e. there the operator (you !) can use this to + fill in and remove media from the changer. + Sometimes named "mailslot". + data transfer - this is the device which reads/writes, i.e. the + CD-ROM / Tape / whatever drive. + +None of these is limited to one: A huge Jukebox could have slots for +123 CD-ROM's, 5 CD-ROM readers (and therefore 6 SCSI ID's: the changer +and each CD-ROM) and 2 transport arms. No problem to handle. + + +How it is implemented +--------------------- + +I implemented the driver as character device driver with a NetBSD-like +ioctl interface. Just grabbed NetBSD's header file and one of the +other linux SCSI device drivers as starting point. The interface +should be source code compatible with NetBSD. So if there is any +software (anybody knows ???) which supports a BSDish changer driver, +it should work with this driver too. + +Over time a few more ioctls where added, volume tag support for example +wasn't covered by the NetBSD ioctl API. + + +Current State +------------- + +Support for more than one transport arm is not implemented yet (and +nobody asked for it so far...). + +I test and use the driver myself with a 35 slot cdrom jukebox from +Grundig. I got some reports telling it works ok with tape autoloaders +(Exabyte, HP and DEC). Some People use this driver with amanda. It +works fine with small (11 slots) and a huge (4 MOs, 88 slots) +magneto-optical Jukebox. Probably with lots of other changers too, most +(but not all :-) people mail me only if it does *not* work... + +I don't have any device lists, neither black-list nor white-list. Thus +it is quite useless to ask me whenever a specific device is supported or +not. In theory every changer device which supports the SCSI-2 media +changer command set should work out-of-the-box with this driver. If it +doesn't, it is a bug. Either within the driver or within the firmware +of the changer device. + + +Using it +-------- + +This is a character device with major number is 86, so use +"mknod /dev/sch0 c 86 0" to create the special file for the driver. + +If the module finds the changer, it prints some messages about the +device [ try "dmesg" if you don't see anything ] and should show up in +/proc/devices. If not.... some changers use ID ? / LUN 0 for the +device and ID ? / LUN 1 for the robot mechanism. But Linux does *not* +look for LUN's other than 0 as default, becauce there are to many +broken devices. So you can try: + + 1) echo "scsi add-single-device 0 0 ID 1" > /proc/scsi/scsi + (replace ID with the SCSI-ID of the device) + 2) boot the kernel with "max_scsi_luns=1" on the command line + (append="max_scsi_luns=1" in lilo.conf should do the trick) + + +Trouble? +-------- + +If you insmod the driver with "insmod debug=1", it will be verbose and +prints a lot of stuff to the syslog. Compiling the kernel with +CONFIG_SCSI_CONSTANTS=y improves the quality of the error messages alot +because the kernel will translate the error codes into human-readable +strings then. + +You can display these messages with the dmesg command (or check the +logfiles). If you email me some question becauce of a problem with the +driver, please include these messages. + + +Insmod options +-------------- + +debug=0/1 + Enable debug messages (see above, default: 0). + +verbose=0/1 + Be verbose (default: 1). + +init=0/1 + Send INITIALIZE ELEMENT STATUS command to the changer + at insmod time (default: 1). + +check_busy=0/1 + When moving media from/to data transfer elements, check + whenever the device is busy and refuse to move if so + (default: 1). + +timeout_init= + timeout for the INITIALIZE ELEMENT STATUS command + (default: 3600). + +timeout_move= + timeout for all other commands (default: 120). + +dt_id=,,... +dt_lun=,,... + These two allow to specify the SCSI ID and LUN for the data + transfer elements. You likely don't need this as the jukebox + should provide this information. But some devices don't ... + +vendor_firsts= +vendor_counts= +vendor_labels= + These insmod options can be used to tell the driver that there + are some vendor-specific element types. Grundig for example + does this. Some jukeboxes have a printer to label fresh burned + CDs, which is addressed as element 0xc000 (type 5). To tell the + driver about this vendor-specific element, use this: + $ insmod ch \ + vendor_firsts=0xc000 \ + vendor_counts=1 \ + vendor_labels=printer + All three insmod options accept up to four comma-separated + values, this way you can configure the element types 5-8. + You likely need the SCSI specs for the device in question to + find the correct values as they are not covered by the SCSI-2 + standard. + + +Credits +------- + +I wrote this driver using the famous mailing-patches-around-the-world +method. With (more or less) help from: + + Daniel Moehwald + Dane Jasper + R. Scott Bailey + Jonathan Corbet + +Special thanks go to + Martin Kuehne +for a old, second-hand (but full functional) cdrom jukebox which I use +to develop/test driver and tools now. + +Have fun, + + Gerd + +-- +Gerd Knorr diff --git a/RegEdit.c b/RegEdit.c new file mode 100644 index 0000000..fa01b88 --- /dev/null +++ b/RegEdit.c @@ -0,0 +1,1795 @@ +/* $XConsortium: RegEdit.c /main/5 1995/07/15 20:44:04 drk $ */ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ +#include +#include +#include +#include "RegEditI.h" + + +/* static forward. move from global in the original Editres code */ +static void _XEditResCheckMessages(); +static void _XEditResPutString8(); +static void _XEditResPut8(); +static void _XEditResPut16(); +static void _XEditResPut32(); +static void _XEditResPutWidgetInfo(); +static void _XEditResResetStream(); +static Boolean _XEditResGet8(); +static Boolean _XEditResGet16(); +static Boolean _XEditResGetSigned16(); +static Boolean _XEditResGet32(); +static Boolean _XEditResGetString8(); +static Boolean _XEditResGetWidgetInfo(); + +/* the only entry point here */ +void +XmdRegisterEditres(Widget toplevel) +{ + XtAddEventHandler(toplevel, (EventMask) 0, TRUE, + _XEditResCheckMessages, NULL); +} + + +/************************************************************ + * + * Dump the content of the R5 lib/Xmu/EditresCom.c module. + * just move global as static. + * + ************************************************************/ + +#define _XEditResPutBool _XEditResPut8 +#define _XEditResPutResourceType _XEditResPut8 + +/************************************************************ + * + * Local structure definitions. + * + ************************************************************/ + +typedef enum { BlockNone, BlockSetValues, BlockAll } EditresBlock; + +typedef struct _SetValuesEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + unsigned short num_entries; /* number of set values requests. */ + char * name; + char * res_type; + XtPointer value; + unsigned short value_len; +} SetValuesEvent; + +typedef struct _SVErrorInfo { + SetValuesEvent * event; + ProtocolStream * stream; + unsigned short * count; + WidgetInfo * entry; +} SVErrorInfo; + +typedef struct _FindChildEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + short x, y; +} FindChildEvent; + +typedef struct _GenericGetEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; + unsigned short num_entries; /* number of set values requests. */ +} GenericGetEvent, GetResEvent, GetGeomEvent; + +/* + * Things that are common to all events. + */ + +typedef struct _AnyEvent { + EditresCommand type; /* first field must be type. */ + WidgetInfo * widgets; +} AnyEvent; + +/* + * The event union. + */ + +typedef union _EditresEvent { + AnyEvent any_event; + SetValuesEvent set_values_event; + GetResEvent get_resources_event; + GetGeomEvent get_geometry_event; + FindChildEvent find_child_event; +} EditresEvent; + +typedef struct _Globals { + EditresBlock block; + SVErrorInfo error_info; + ProtocolStream stream; + ProtocolStream * command_stream; /* command stream. */ +} Globals; + +#define CURRENT_PROTOCOL_VERSION 4L + +#define streq(a,b) (strcmp( (a), (b) ) == 0) + +static Atom res_editor_command, res_editor_protocol, client_value; + +static Globals globals; + +static void SendFailure(), SendCommand(), InsertWidget(), ExecuteCommand(); +static void FreeEvent(), ExecuteSetValues(), ExecuteGetGeometry(); +static void ExecuteGetResources(); + +static void GetCommand(); +static void LoadResources(); +static Boolean IsChild(); +static void DumpChildren(); +static char *DumpWidgets(), *DoSetValues(), *DoFindChild(); +static char *DoGetGeometry(), *DoGetResources(); + +/************************************************************ + * + * Resource Editor Communication Code + * + ************************************************************/ + +/* Function Name: _XEditResCheckMessages + * Description: This callback routine is set on all shell widgets, + * and checks to see if a client message event + * has come from the resource editor. + * Arguments: w - the shell widget. + * data - *** UNUSED *** + * event - The X Event that triggered this handler. + * cont - *** UNUSED ***. + * Returns: none. + */ + +/* ARGSUSED */ +static void +_XEditResCheckMessages(w, data, event, cont) +Widget w; +XtPointer data; +XEvent *event; +Boolean *cont; +{ + Time time; + ResIdent ident; + static Boolean first_time = FALSE; + static Atom res_editor, res_comm; + Display * dpy; + + if (event->type == ClientMessage) { + XClientMessageEvent * c_event = (XClientMessageEvent *) event; + dpy = XtDisplay(w); + + if (!first_time) { + first_time = TRUE; + res_editor = XInternAtom(dpy, EDITRES_NAME, False); + res_editor_command = XInternAtom(dpy, EDITRES_COMMAND_ATOM, False); + res_editor_protocol = XInternAtom(dpy, EDITRES_PROTOCOL_ATOM, + False); + + /* Used in later procedures. */ + client_value = XInternAtom(dpy, EDITRES_CLIENT_VALUE, False); + LoadResources(w); + } + + if ((c_event->message_type != res_editor) || + (c_event->format != EDITRES_SEND_EVENT_FORMAT)) + return; + + time = c_event->data.l[0]; + res_comm = c_event->data.l[1]; + ident = (ResIdent) c_event->data.l[2]; + if (c_event->data.l[3] != CURRENT_PROTOCOL_VERSION) { + _XEditResResetStream(&globals.stream); + _XEditResPut8(&globals.stream, CURRENT_PROTOCOL_VERSION); + SendCommand(w, res_comm, ident, ProtocolMismatch, &globals.stream); + return; + } + + XtGetSelectionValue(w, res_comm, res_editor_command, + GetCommand, (XtPointer) (long) ident, time); + } +} + +/* Function Name: BuildEvent + * Description: Takes the info out the protocol stream an constructs + * the proper event structure. + * Arguments: w - widget to own selection, in case of error. + * sel - selection to send error message beck in. + * data - the data for the request. + * ident - the id number we are looking for. + * length - length of request. + * Returns: the event, or NULL. + */ + +#define ERROR_MESSAGE ("Client: Improperly formatted protocol request") + +static EditresEvent * +BuildEvent(w, sel, data, ident, length) +Widget w; +Atom sel; +XtPointer data; +ResIdent ident; +unsigned long length; +{ + EditresEvent * event; + ProtocolStream alloc_stream, *stream; + unsigned char temp; + register unsigned int i; + + stream = &alloc_stream; /* easier to think of it this way... */ + + stream->current = stream->top = (unsigned char *) data; + stream->size = HEADER_SIZE; /* size of header. */ + + /* + * Retrieve the Header. + */ + + if (length < HEADER_SIZE) { + SendFailure(w, sel, ident, Failure, ERROR_MESSAGE); + return(NULL); + } + + (void) _XEditResGet8(stream, &temp); + if (temp != ident) /* Id's don't match, ignore request. */ + return(NULL); + + event = (EditresEvent *) XtCalloc(sizeof(EditresEvent), 1); + + (void) _XEditResGet8(stream, &temp); + event->any_event.type = (EditresCommand) temp; + (void) _XEditResGet32(stream, &(stream->size)); + stream->top = stream->current; /* reset stream to top of value.*/ + + /* + * Now retrieve the data segment. + */ + + switch(event->any_event.type) { + case SendWidgetTree: + break; /* no additional info */ + case SetValues: + { + SetValuesEvent * sv_event = (SetValuesEvent *) event; + + if ( !(_XEditResGetString8(stream, &(sv_event->name)) && + _XEditResGetString8(stream, &(sv_event->res_type)))) + { + goto done; + } + + /* + * Since we need the value length, we have to pull the + * value out by hand. + */ + + if (!_XEditResGet16(stream, &(sv_event->value_len))) + goto done; + + sv_event->value = XtMalloc(sizeof(char) * + (sv_event->value_len + 1)); + + for (i = 0; i < sv_event->value_len; i++) { + if (!_XEditResGet8(stream, + (unsigned char *) sv_event->value + i)) + { + goto done; + } + } + ((char*)sv_event->value)[i] = '\0'; /* NULL terminate that sucker. */ + + if (!_XEditResGet16(stream, &(sv_event->num_entries))) + goto done; + + sv_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), sv_event->num_entries); + + for (i = 0; i < sv_event->num_entries; i++) { + if (!_XEditResGetWidgetInfo(stream, sv_event->widgets + i)) + goto done; + } + } + break; + case FindChild: + { + FindChildEvent * find_event = (FindChildEvent *) event; + + find_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), 1); + + if (!(_XEditResGetWidgetInfo(stream, find_event->widgets) && + _XEditResGetSigned16(stream, &(find_event->x)) && + _XEditResGetSigned16(stream, &(find_event->y)))) + { + goto done; + } + + } + break; + case GetGeometry: + case GetResources: + { + GenericGetEvent * get_event = (GenericGetEvent *) event; + + if (!_XEditResGet16(stream, &(get_event->num_entries))) + goto done; + + get_event->widgets = (WidgetInfo *) + XtCalloc(sizeof(WidgetInfo), get_event->num_entries); + for (i = 0; i < get_event->num_entries; i++) { + if (!_XEditResGetWidgetInfo(stream, get_event->widgets + i)) + goto done; + } + } + break; + default: + { + char buf[BUFSIZ]; + + sprintf(buf, "Unknown Protocol request %d.",event->any_event.type); + SendFailure(w, sel, ident, buf); + return(NULL); + } + } + return(event); + + done: + + SendFailure(w, sel, ident, ERROR_MESSAGE); + FreeEvent(event); + return(NULL); +} + +/* Function Name: FreeEvent + * Description: Frees the event structure and any other pieces + * in it that need freeing. + * Arguments: event - the event to free. + * Returns: none. + */ + +static void +FreeEvent(event) +EditresEvent * event; +{ + if (event->any_event.widgets != NULL) { + XtFree((char *)event->any_event.widgets->ids); + XtFree((char *)event->any_event.widgets); + } + + if (event->any_event.type == SetValues) { + XtFree(event->set_values_event.name); /* XtFree does not free if */ + XtFree(event->set_values_event.res_type); /* value is NULL. */ + } + + XtFree((char *)event); +} + +/* Function Name: GetCommand + * Description: Gets the Command out of the selection asserted by the + * resource manager. + * Arguments: (See Xt XtConvertSelectionProc) + * data - contains the ident number for the command. + * Returns: none. + */ + +/* ARGSUSED */ +static void +GetCommand(w, data, selection, type, value, length, format) +Widget w; +XtPointer data, value; +Atom *selection, *type; +unsigned long *length; +int * format; +{ + ResIdent ident = (ResIdent) (long) data; + EditresEvent * event; + + if ( (*type != res_editor_protocol) || (*format != EDITRES_FORMAT) ) + return; + + if ((event = BuildEvent(w, *selection, value, ident, *length)) != NULL) { + ExecuteCommand(w, *selection, ident, event); + FreeEvent(event); + } +} + +/* Function Name: ExecuteCommand + * Description: Executes a command string received from the + * resource editor. + * Arguments: w - a widget. + * command - the command to execute. + * value - the associated with the command. + * Returns: none. + * + * NOTES: munges str + */ + +/* ARGSUSED */ +static void +ExecuteCommand(w, sel, ident, event) +Widget w; +Atom sel; +ResIdent ident; +EditresEvent * event; +{ + char * (*func)(); + char * str; + + if (globals.block == BlockAll) { + SendFailure(w, sel, ident, + "This client has blocked all Editres commands."); + return; + } + else if ((globals.block == BlockSetValues) && + (event->any_event.type == SetValues)) { + SendFailure(w, sel, ident, + "This client has blocked all SetValues requests."); + return; + } + + switch(event->any_event.type) { + case SendWidgetTree: + func = DumpWidgets; + break; + case SetValues: + func = DoSetValues; + break; + case FindChild: + func = DoFindChild; + break; + case GetGeometry: + func = DoGetGeometry; + break; + case GetResources: + func = DoGetResources; + break; + default: + { + char buf[BUFSIZ]; + sprintf(buf,"Unknown Protocol request %d.",event->any_event.type); + SendFailure(w, sel, ident, buf); + return; + } + } + + _XEditResResetStream(&globals.stream); + if ((str = (*func)(w, event, &globals.stream)) == NULL) + SendCommand(w, sel, ident, PartialSuccess, &globals.stream); + else { + SendFailure(w, sel, ident, str); + XtFree(str); + } +} + +/* Function Name: ConvertReturnCommand + * Description: Converts a selection. + * Arguments: w - the widget that owns the selection. + * selection - selection to convert. + * target - target type for this selection. + * type_ret - type of the selection. + * value_ret - selection value; + * length_ret - lenght of this selection. + * format_ret - the format the selection is in. + * Returns: True if conversion was sucessful. + */ + +/* ARGSUSED */ +static Boolean +ConvertReturnCommand(w, selection, target, + type_ret, value_ret, length_ret, format_ret) +Widget w; +Atom * selection, * target, * type_ret; +XtPointer *value_ret; +unsigned long * length_ret; +int * format_ret; +{ + /* + * I assume the intrinsics give me the correct selection back. + */ + + if ((*target != client_value)) + return(FALSE); + + *type_ret = res_editor_protocol; + *value_ret = (XtPointer) globals.command_stream->real_top; + *length_ret = globals.command_stream->size + HEADER_SIZE; + *format_ret = EDITRES_FORMAT; + + return(TRUE); +} + +/* Function Name: CommandDone + * Description: done with the selection. + * Arguments: *** UNUSED *** + * Returns: none. + */ + +/* ARGSUSED */ +static void +CommandDone(widget, selection, target) +Widget widget; +Atom *selection; +Atom *target; +{ + /* Keep the toolkit from automaticaly freeing the selection value */ +} + +/* Function Name: SendFailure + * Description: Sends a failure message. + * Arguments: w - the widget to own the selection. + * sel - the selection to assert. + * ident - the identifier. + * str - the error message. + * Returns: none. + */ + +static void +SendFailure(w, sel, ident, str) +Widget w; +Atom sel; +ResIdent ident; +char * str; +{ + _XEditResResetStream(&globals.stream); + _XEditResPutString8(&globals.stream, str); + SendCommand(w, sel, ident, Failure, &globals.stream); +} + +/* Function Name: BuildReturnPacket + * Description: Builds a return packet, given the data to send. + * Arguments: ident - the identifier. + * command - the command code. + * stream - the protocol stream. + * Returns: packet - the packet to send. + */ + +static XtPointer +BuildReturnPacket(ident, command, stream) +ResIdent ident; +EditresCommand command; +ProtocolStream * stream; +{ + long old_alloc, old_size; + unsigned char * old_current; + + /* + * We have cleverly keep enough space at the top of the header + * for the return protocol stream, so all we have to do is + * fill in the space. + */ + + /* + * Fool the insert routines into putting the header in the right + * place while being damn sure not to realloc (that would be very bad. + */ + + old_current = stream->current; + old_alloc = stream->alloc; + old_size = stream->size; + + stream->current = stream->real_top; + stream->alloc = stream->size + (2 * HEADER_SIZE); + + _XEditResPut8(stream, ident); + _XEditResPut8(stream, (unsigned char) command); + _XEditResPut32(stream, old_size); + + stream->alloc = old_alloc; + stream->current = old_current; + stream->size = old_size; + + return((XtPointer) stream->real_top); +} + +/* Function Name: SendCommand + * Description: Builds a return command line. + * Arguments: w - the widget to own the selection. + * sel - the selection to assert. + * ident - the identifier. + * command - the command code. + * stream - the protocol stream. + * Returns: none. + */ + +static void +SendCommand(w, sel, ident, command, stream) +Widget w; +Atom sel; +ResIdent ident; +EditresCommand command; +ProtocolStream * stream; +{ + BuildReturnPacket(ident, command, stream); + globals.command_stream = stream; + +/* + * I REALLY want to own the selection. Since this was not triggered + * by a user action, and I am the only one using this atom it is safe to + * use CurrentTime. + */ + + XtOwnSelection(w, sel, CurrentTime, + ConvertReturnCommand, NULL, CommandDone); +} + +/************************************************************ + * + * Generic Utility Functions. + * + ************************************************************/ + +/* Function Name: FindChildren + * Description: Retuns all children (popup, normal and otherwise) + * of this widget + * Arguments: parent - the parent widget. + * children - the list of children. + * normal - return normal children. + * popup - return popup children. + * Returns: the number of children. + */ + +static int +FindChildren(parent, children, normal, popup) +Widget parent, **children; +Boolean normal, popup; +{ + CompositeWidget cw = (CompositeWidget) parent; + int i, num_children, current = 0; + + num_children = 0; + + if (XtIsWidget(parent) && popup) + num_children += parent->core.num_popups; + + if (XtIsComposite(parent) && normal) + num_children += cw->composite.num_children; + + if (num_children == 0) { + *children = NULL; + return(0); + } + + *children =(Widget*) XtMalloc((Cardinal) sizeof(Widget) * num_children); + + if (XtIsComposite(parent) && normal) + for (i = 0; i < cw->composite.num_children; i++,current++) + (*children)[current] = cw->composite.children[i]; + + if (XtIsWidget(parent) && popup) + for ( i = 0; i < parent->core.num_popups; i++, current++) + (*children)[current] = parent->core.popup_list[i]; + + return(num_children); +} + +/* Function Name: IsChild + * Description: check to see of child is a child of parent. + * Arguments: top - the top of the tree. + * parent - the parent widget. + * child - the child. + * Returns: none. + */ + +static Boolean +IsChild(top, parent, child) +Widget top, parent, child; +{ + int i, num_children; + Widget * children; + + if (parent == NULL) + return(top == child); + + num_children = FindChildren(parent, &children, TRUE, TRUE); + + for (i = 0; i < num_children; i++) { + if (children[i] == child) { + XtFree((char *)children); + return(TRUE); + } + } + + XtFree((char *)children); + return(FALSE); +} + +/* Function Name: VerifyWidget + * Description: Makes sure all the widgets still exist. + * Arguments: w - any widget in the tree. + * info - the info about the widget to verify. + * Returns: an error message or NULL. + */ + +static char * +VerifyWidget(w, info) +Widget w; +WidgetInfo *info; +{ + Widget top; + + register int count; + register Widget parent; + register unsigned long * child; + + for (top = w; XtParent(top) != NULL; top = XtParent(top)) {} + + parent = NULL; + child = info->ids; + count = 0; + + while (TRUE) { + if (!IsChild(top, parent, (Widget) *child)) + return(XtNewString("This widget no longer exists in the client.")); + + if (++count == info->num_widgets) + break; + + parent = (Widget) *child++; + } + + info->real_widget = (Widget) *child; + return(NULL); +} + +/************************************************************ + * + * Code to Perform SetValues operations. + * + ************************************************************/ + + +/* Function Name: DoSetValues + * Description: performs the setvalues requested. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL. + */ + +static char * +DoSetValues(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + char * str; + register unsigned i; + unsigned short count = 0; + SetValuesEvent * sv_event = (SetValuesEvent *) event; + + _XEditResPut16(stream, count); /* insert 0, will be overwritten later. */ + + for (i = 0 ; i < sv_event->num_entries; i++) { + if ((str = VerifyWidget(w, &(sv_event->widgets[i]))) != NULL) { + _XEditResPutWidgetInfo(stream, &(sv_event->widgets[i])); + _XEditResPutString8(stream, str); + XtFree(str); + count++; + } + else + ExecuteSetValues(sv_event->widgets[i].real_widget, + sv_event, sv_event->widgets + i, stream, &count); + } + + /* + * Overwrite the first 2 bytes with the real count. + */ + + *(stream->top) = count >> XER_NBBY; + *(stream->top + 1) = count; + return(NULL); +} + +/* Function Name: HandleToolkitErrors + * Description: Handles X Toolkit Errors. + * Arguments: name - name of the error. + * type - type of the error. + * class - class of the error. + * msg - the default message. + * params, num_params - the extra parameters for this message. + * Returns: none. + */ + +/* ARGSUSED */ +static void +HandleToolkitErrors(name, type, class, msg, params, num_params) +String name, type, class, msg, *params; +Cardinal * num_params; +{ + SVErrorInfo * info = &globals.error_info; + char buf[BUFSIZ]; + + if ( streq(name, "unknownType") ) + sprintf(buf, "The `%s' resource is not used by this widget.", + info->event->name); + else if ( streq(name, "noColormap") ) + sprintf(buf, msg, params[0]); + else if (streq(name, "conversionFailed") || streq(name, "conversionError")) + { + if (streq(info->event->value, XtRString)) + sprintf(buf, + "Could not convert the string '%s' for the `%s' resource.", + (char*)info->event->value, info->event->name); + else + sprintf(buf, "Could not convert the `%s' resource.", + info->event->name); + } + else + sprintf(buf, "Name: %s, Type: %s, Class: %s, Msg: %s", + name, type, class, msg); + + /* + * Insert this info into the protocol stream, and update the count. + */ + + (*(info->count))++; + _XEditResPutWidgetInfo(info->stream, info->entry); + _XEditResPutString8(info->stream, buf); +} + +/* Function Name: ExecuteSetValues + * Description: Performs a setvalues for a given command. + * Arguments: w - the widget to perform the set_values on. + * sv_event - the set values event. + * sv_info - the set_value info. + * Returns: none. + */ + +static void +ExecuteSetValues(w, sv_event, entry, stream, count) +Widget w; +SetValuesEvent * sv_event; +WidgetInfo * entry; +ProtocolStream * stream; +unsigned short * count; +{ + XtErrorMsgHandler old; + + SVErrorInfo * info = &globals.error_info; + info->event = sv_event; /* No data can be passed to */ + info->stream = stream; /* an error handler, so we */ + info->count = count; /* have to use a global, YUCK... */ + info->entry = entry; + + old = XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w), + HandleToolkitErrors); + + XtVaSetValues(w, XtVaTypedArg, + sv_event->name, sv_event->res_type, + sv_event->value, sv_event->value_len, + NULL); + + (void)XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w), old); +} + + +/************************************************************ + * + * Code for Creating and dumping widget tree. + * + ************************************************************/ + +/* Function Name: DumpWidgets + * Description: Given a widget it builds a protocol packet + * containing the entire widget heirarchy. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +/* ARGSUSED */ +static char * +DumpWidgets(w, event, stream) +Widget w; +EditresEvent * event; /* UNUSED */ +ProtocolStream * stream; +{ + unsigned short count = 0; + + /* Find Tree's root. */ + for ( ; XtParent(w) != NULL; w = XtParent(w)) {} + + /* + * hold space for count, overwritten later. + */ + + _XEditResPut16(stream, (unsigned int) 0); + + DumpChildren(w, stream, &count); + + /* + * Overwrite the first 2 bytes with the real count. + */ + + *(stream->top) = count >> XER_NBBY; + *(stream->top + 1) = count; + return(NULL); +} + +/* Function Name: DumpChildren + * Description: Adds a child's name to the list. + * Arguments: w - the widget to dump. + * stream - the stream to dump to. + * count - number of dumps we have performed. + * Returns: none. + */ + +/* This is a trick/kludge. To make shared libraries happier (linking + * against Xmu but not linking against Xt, and apparently even work + * as we desire on SVR4, we need to avoid an explicit data reference + * to applicationShellWidgetClass. XtIsTopLevelShell is known + * (implementation dependent assumption!) to use a bit flag. So we + * go that far. Then, we test whether it is an applicationShellWidget + * class by looking for an explicit class name. Seems pretty safe. + */ +static Bool isApplicationShell(w) + Widget w; +{ + register WidgetClass c; + + if (!XtIsTopLevelShell(w)) + return False; + for (c = XtClass(w); c; c = c->core_class.superclass) { + if (!strcmp(c->core_class.class_name, "ApplicationShell")) + return True; + } + return False; +} + +static void +DumpChildren(w, stream, count) +Widget w; +ProtocolStream * stream; +unsigned short *count; +{ + int i, num_children; + Widget *children; + unsigned long window; + char * class; + + (*count)++; + + InsertWidget(stream, w); /* Insert the widget into the stream. */ + + _XEditResPutString8(stream, XtName(w)); /* Insert name */ + + if (isApplicationShell(w)) + class = ((ApplicationShellWidget) w)->application.class; + else + class = XtClass(w)->core_class.class_name; + + _XEditResPutString8(stream, class); /* Insert class */ + + if (XtIsWidget(w)) + if (XtIsRealized(w)) + window = XtWindow(w); + else + window = EDITRES_IS_UNREALIZED; + else + window = EDITRES_IS_OBJECT; + + _XEditResPut32(stream, window); /* Insert window id. */ + + /* + * Find children and recurse. + */ + + num_children = FindChildren(w, &children, TRUE, TRUE); + for (i = 0; i < num_children; i++) + DumpChildren(children[i], stream, count); + + XtFree((char *)children); +} + +/************************************************************ + * + * Code for getting the geometry of widgets. + * + ************************************************************/ + +/* Function Name: DoGetGeometry + * Description: retrieves the Geometry of each specified widget. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +static char * +DoGetGeometry(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + unsigned i; + char * str; + GetGeomEvent * geom_event = (GetGeomEvent *) event; + + _XEditResPut16(stream, geom_event->num_entries); + + for (i = 0 ; i < geom_event->num_entries; i++) { + + /* + * Send out the widget id. + */ + + _XEditResPutWidgetInfo(stream, &(geom_event->widgets[i])); + if ((str = VerifyWidget(w, &(geom_event->widgets[i]))) != NULL) { + _XEditResPutBool(stream, True); /* an error occured. */ + _XEditResPutString8(stream, str); /* set message. */ + XtFree(str); + } + else + ExecuteGetGeometry(geom_event->widgets[i].real_widget, stream); + } + return(NULL); +} + +/* Function Name: ExecuteGetGeometry + * Description: Gets the geometry for each widget specified. + * Arguments: w - the widget to get geom on. + * stream - stream to append to. + * Returns: True if no error occured. + */ + +static void +ExecuteGetGeometry(w, stream) +Widget w; +ProtocolStream * stream; +{ + int i; + Boolean mapped_when_man; + Dimension width, height, border_width; + Arg args[8]; + Cardinal num_args = 0; + Position x, y; + + if ( !XtIsRectObj(w) || (XtIsWidget(w) && !XtIsRealized(w)) ) { + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, False); /* not visable. */ + for (i = 0; i < 5; i++) /* fill in extra space with 0's. */ + _XEditResPut16(stream, 0); + return; + } + + XtSetArg(args[num_args], XtNwidth, &width); num_args++; + XtSetArg(args[num_args], XtNheight, &height); num_args++; + XtSetArg(args[num_args], XtNborderWidth, &border_width); num_args++; + XtSetArg(args[num_args], XtNmappedWhenManaged, &mapped_when_man); + num_args++; + XtGetValues(w, args, num_args); + + if (!(XtIsManaged(w) && mapped_when_man) && XtIsWidget(w)) { + XWindowAttributes attrs; + + /* + * The toolkit does not maintain mapping state, we have + * to go to the server. + */ + + if (XGetWindowAttributes(XtDisplay(w), XtWindow(w), &attrs) != 0) { + if (attrs.map_state != IsViewable) { + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, False); /* not visable. */ + for (i = 0; i < 5; i++) /* fill in extra space with 0's. */ + _XEditResPut16(stream, 0); + return; + } + } + else { + _XEditResPut8(stream, True); /* Error occured. */ + _XEditResPutString8(stream, "XGetWindowAttributes failed."); + return; + } + } + + XtTranslateCoords(w, -((int) border_width), -((int) border_width), &x, &y); + + _XEditResPutBool(stream, False); /* no error. */ + _XEditResPutBool(stream, True); /* Visable. */ + _XEditResPut16(stream, x); + _XEditResPut16(stream, y); + _XEditResPut16(stream, width); + _XEditResPut16(stream, height); + _XEditResPut16(stream, border_width); +} + +/************************************************************ + * + * Code for executing FindChild. + * + ************************************************************/ + +/* Function Name: PositionInChild + * Description: returns true if this location is in the child. + * Arguments: child - the child widget to check. + * x, y - location of point to check in the parent's + * coord space. + * Returns: TRUE if the position is in this child. + */ + +static Boolean +PositionInChild(child, x, y) +Widget child; +int x, y; +{ + Arg args[6]; + Cardinal num; + Dimension width, height, border_width; + Position child_x, child_y; + Boolean mapped_when_managed; + + if (!XtIsRectObj(child)) /* we must at least be a rect obj. */ + return(FALSE); + + num = 0; + XtSetArg(args[num], XtNmappedWhenManaged, &mapped_when_managed); num++; + XtSetArg(args[num], XtNwidth, &width); num++; + XtSetArg(args[num], XtNheight, &height); num++; + XtSetArg(args[num], XtNx, &child_x); num++; + XtSetArg(args[num], XtNy, &child_y); num++; + XtSetArg(args[num], XtNborderWidth, &border_width); num++; + XtGetValues(child, args, num); + + /* + * The only way we will know of the widget is mapped is to see if + * mapped when managed is True and this is a managed child. Otherwise + * we will have to ask the server if this window is mapped. + */ + + if (XtIsWidget(child) && !(mapped_when_managed && XtIsManaged(child)) ) { + XWindowAttributes attrs; + + if (XGetWindowAttributes(XtDisplay(child), + XtWindow(child), &attrs) != 0) { + /* oops */ + } + else if (attrs.map_state != IsViewable) + return(FALSE); + } + + return (x >= child_x) && + (x <= (child_x + (Position)width + 2 * (Position)border_width)) && + (y >= child_y) && + (y <= (child_y + (Position)height + 2 * (Position)border_width)); +} + +/* Function Name: _FindChild + * Description: Finds the child that actually contatians the point shown. + * Arguments: parent - a widget that is known to contain the point + * specified. + * x, y - The point in coordinates relative to the + * widget specified. + * Returns: none. + */ + +static Widget +_FindChild(parent, x, y) +Widget parent; +int x, y; +{ + Widget * children; + int i = FindChildren(parent, &children, TRUE, FALSE); + + while (i > 0) { + i--; + + if (PositionInChild(children[i], x, y)) { + Widget child = children[i]; + + XtFree((char *)children); + return(_FindChild(child, x - child->core.x, y - child->core.y)); + } + } + + XtFree((char *)children); + return(parent); +} + +/* Function Name: DoFindChild + * Description: finds the child that contains the location specified. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: an allocated error message if something went horribly + * wrong and no set values were performed, else NULL. + */ + +static char * +DoFindChild(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + char * str; + Widget parent, child; + Position parent_x, parent_y; + FindChildEvent * find_event = (FindChildEvent *) event; + + if ((str = VerifyWidget(w, find_event->widgets)) != NULL) + return(str); + + parent = find_event->widgets->real_widget; + + XtTranslateCoords(parent, (Position) 0, (Position) 0, + &parent_x, &parent_y); + + child = _FindChild(parent, find_event->x - (int) parent_x, + find_event->y - (int) parent_y); + + InsertWidget(stream, child); + return(NULL); +} + +/************************************************************ + * + * Procedures for performing GetResources. + * + ************************************************************/ + +/* Function Name: DoGetResources + * Description: Gets the Resources associated with the widgets passed. + * Arguments: w - a widget in the tree. + * event - the event that caused this action. + * stream - the protocol stream to add. + * Returns: NULL + */ + +static char * +DoGetResources(w, event, stream) +Widget w; +EditresEvent * event; +ProtocolStream * stream; +{ + unsigned int i; + char * str; + GetResEvent * res_event = (GetResEvent *) event; + + _XEditResPut16(stream, res_event->num_entries); /* number of replys */ + + for (i = 0 ; i < res_event->num_entries; i++) { + /* + * Send out the widget id. + */ + _XEditResPutWidgetInfo(stream, &(res_event->widgets[i])); + if ((str = VerifyWidget(w, &(res_event->widgets[i]))) != NULL) { + _XEditResPutBool(stream, True); /* an error occured. */ + _XEditResPutString8(stream, str); /* set message. */ + XtFree(str); + } + else { + _XEditResPutBool(stream, False); /* no error occured. */ + ExecuteGetResources(res_event->widgets[i].real_widget, + stream); + } + } + return(NULL); +} + +/* Function Name: ExecuteGetResources. + * Description: Gets the resources for any individual widget. + * Arguments: w - the widget to get resources on. + * stream - the protocol stream. + * Returns: none. + */ + +static void +ExecuteGetResources(w, stream) +Widget w; +ProtocolStream * stream; +{ + XtResourceList norm_list, cons_list; + Cardinal num_norm, num_cons; + register int i; + + /* + * Get Normal Resources. + */ + + XtGetResourceList(XtClass(w), &norm_list, &num_norm); + + if (XtParent(w) != NULL) + XtGetConstraintResourceList(XtClass(XtParent(w)),&cons_list,&num_cons); + else + num_cons = 0; + + _XEditResPut16(stream, num_norm + num_cons); /* how many resources. */ + + /* + * Insert all the normal resources. + */ + + for ( i = 0; i < (int) num_norm; i++) { + _XEditResPutResourceType(stream, NormalResource); + _XEditResPutString8(stream, norm_list[i].resource_name); + _XEditResPutString8(stream, norm_list[i].resource_class); + _XEditResPutString8(stream, norm_list[i].resource_type); + } + XtFree((char *) norm_list); + + /* + * Insert all the constraint resources. + */ + + if (num_cons > 0) { + for ( i = 0; i < (int) num_cons; i++) { + _XEditResPutResourceType(stream, ConstraintResource); + _XEditResPutString8(stream, cons_list[i].resource_name); + _XEditResPutString8(stream, cons_list[i].resource_class); + _XEditResPutString8(stream, cons_list[i].resource_type); + } + XtFree((char *) cons_list); + } +} + +/************************************************************ + * + * Code for inserting values into the protocol stream. + * + ************************************************************/ + +/* Function Name: InsertWidget + * Description: Inserts the full parent heirarchy of this + * widget into the protocol stream as a widget list. + * Arguments: stream - the protocol stream. + * w - the widget to insert. + * Returns: none + */ + +static void +InsertWidget(stream, w) +ProtocolStream * stream; +Widget w; +{ + Widget temp; + unsigned long * widget_list; + register int i, num_widgets; + + for (temp = w, i = 0; temp != 0; temp = XtParent(temp), i++) {} + + num_widgets = i; + widget_list = (unsigned long *) + XtMalloc(sizeof(unsigned long) * num_widgets); + + /* + * Put the widgets into the list. + * make sure that they are inserted in the list from parent -> child. + */ + + for (i--, temp = w; temp != NULL; temp = XtParent(temp), i--) + widget_list[i] = (unsigned long) temp; + + _XEditResPut16(stream, num_widgets); /* insert number of widgets. */ + for (i = 0; i < num_widgets; i++) /* insert Widgets themselves. */ + _XEditResPut32(stream, widget_list[i]); + + XtFree((char *)widget_list); +} + +/************************************************************ + * + * All of the following routines are public. + * + ************************************************************/ + +/* Function Name: _XEditResPutString8 + * Description: Inserts a string into the protocol stream. + * Arguments: stream - stream to insert string into. + * str - string to insert. + * Returns: none. + */ + +static void +_XEditResPutString8(stream, str) +ProtocolStream * stream; +char * str; +{ + int i, len = strlen(str); + + _XEditResPut16(stream, len); + for (i = 0 ; i < len ; i++, str++) + _XEditResPut8(stream, *str); +} + +/* Function Name: _XEditResPut8 + * Description: Inserts an 8 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: none + */ + +static void +_XEditResPut8(stream, value) +ProtocolStream * stream; +unsigned int value; +{ + unsigned char temp; + + if (stream->size >= stream->alloc) { + stream->alloc += 100; + stream->real_top = (unsigned char *) XtRealloc( + (char *)stream->real_top, + stream->alloc + HEADER_SIZE); + stream->top = stream->real_top + HEADER_SIZE; + stream->current = stream->top + stream->size; + } + + temp = (unsigned char) (value & BYTE_MASK); + *((stream->current)++) = temp; + (stream->size)++; +} + +/* Function Name: _XEditResPut16 + * Description: Inserts a 16 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: void + */ + +static void +_XEditResPut16(stream, value) +ProtocolStream * stream; +unsigned int value; +{ + _XEditResPut8(stream, (value >> XER_NBBY) & BYTE_MASK); + _XEditResPut8(stream, value & BYTE_MASK); +} + +/* Function Name: _XEditResPut32 + * Description: Inserts a 32 bit integer into the protocol stream. + * Arguments: stream - stream to insert string into. + * value - value to insert. + * Returns: void + */ + +static void +_XEditResPut32(stream, value) +ProtocolStream * stream; +unsigned long value; +{ + int i; + + for (i = 3; i >= 0; i--) + _XEditResPut8(stream, (value >> (XER_NBBY*i)) & BYTE_MASK); +} + +/* Function Name: _XEditResPutWidgetInfo + * Description: Inserts the widget info into the protocol stream. + * Arguments: stream - stream to insert widget info into. + * info - info to insert. + * Returns: none + */ + +static void +_XEditResPutWidgetInfo(stream, info) +ProtocolStream * stream; +WidgetInfo * info; +{ + unsigned int i; + + _XEditResPut16(stream, info->num_widgets); + for (i = 0; i < info->num_widgets; i++) + _XEditResPut32(stream, info->ids[i]); +} + +/************************************************************ + * + * Code for retrieving values from the protocol stream. + * + ************************************************************/ + +/* Function Name: _XEditResResetStream + * Description: resets the protocol stream + * Arguments: stream - the stream to reset. + * Returns: none. + */ + +static void +_XEditResResetStream(stream) +ProtocolStream * stream; +{ + stream->current = stream->top; + stream->size = 0; + if (stream->real_top == NULL) { + stream->real_top = (unsigned char *) XtRealloc( + (char *)stream->real_top, + stream->alloc + HEADER_SIZE); + stream->top = stream->real_top + HEADER_SIZE; + stream->current = stream->top + stream->size; + } +} + +/* + * NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + * + * The only modified field if the "current" field. + * + * The only fields that must be set correctly are the "current", "top" + * and "size" fields. + */ + +/* Function Name: _XEditResGetg8 + * Description: Retrieves an unsigned 8 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet8(stream, val) +ProtocolStream * stream; +unsigned char * val; +{ + if (stream->size < (stream->current - stream->top)) + return(FALSE); + + *val = *((stream->current)++); + return(TRUE); +} + +/* Function Name: _XEditResGet16 + * Description: Retrieves an unsigned 16 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet16(stream, val) +ProtocolStream * stream; +unsigned short * val; +{ + unsigned char temp1, temp2; + + if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) ) + return(FALSE); + + *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2)); + return(TRUE); +} + +/* Function Name: _XEditResGetSigned16 + * Description: Retrieves an signed 16 bit value from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGetSigned16(stream, val) +ProtocolStream * stream; +short * val; +{ + unsigned char temp1, temp2; + + if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) ) + return(FALSE); + + if (temp1 & (1 << (XER_NBBY - 1))) { /* If the sign bit is active. */ + *val = -1; /* store all 1's */ + *val &= (temp1 << XER_NBBY); /* Now and in the MSB */ + *val &= temp2; /* and LSB */ + } + else + *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2)); + + return(TRUE); +} + +/* Function Name: _XEditResGet32 + * Description: Retrieves an unsigned 32 bit value + * from the protocol stream. + * Arguments: stream. + * val - a pointer to value to return. + * Returns: TRUE if sucessful. + */ + +static Boolean +_XEditResGet32(stream, val) +ProtocolStream * stream; +unsigned long * val; +{ + unsigned short temp1, temp2; + + if ( !(_XEditResGet16(stream, &temp1) && _XEditResGet16(stream, &temp2)) ) + return(FALSE); + + *val = (((unsigned short) temp1 << (XER_NBBY * 2)) + + ((unsigned short) temp2)); + return(TRUE); +} + +/* Function Name: _XEditResGetString8 + * Description: Retrieves an 8 bit string value from the protocol stream. + * Arguments: stream - the protocol stream + * str - the string to retrieve. + * Returns: True if retrieval was successful. + */ + +static Boolean +_XEditResGetString8(stream, str) +ProtocolStream * stream; +char ** str; +{ + unsigned short len; + register unsigned i; + + if (!_XEditResGet16(stream, &len)) { + return(FALSE); + } + + *str = XtMalloc(sizeof(char) * (len + 1)); + + for (i = 0; i < len; i++) { + if (!_XEditResGet8(stream, (unsigned char *) *str + i)) { + XtFree(*str); + *str = NULL; + return(FALSE); + } + } + (*str)[i] = '\0'; /* NULL terminate that sucker. */ + return(TRUE); +} + +/* Function Name: _XEditResGetWidgetInfo + * Description: Retrieves the list of widgets that follow and stores + * them in the widget info structure provided. + * Arguments: stream - the protocol stream + * info - the widget info struct to store into. + * Returns: True if retrieval was successful. + */ + +static Boolean +_XEditResGetWidgetInfo(stream, info) +ProtocolStream * stream; +WidgetInfo * info; +{ + unsigned int i; + + if (!_XEditResGet16(stream, &(info->num_widgets))) + return(FALSE); + + info->ids = (unsigned long *) XtMalloc(sizeof(long) * (info->num_widgets)); + + for (i = 0; i < info->num_widgets; i++) { + if (!_XEditResGet32(stream, info->ids + i)) { + XtFree((char *)info->ids); + info->ids = NULL; + return(FALSE); + } + } + return(TRUE); +} + +/************************************************************ + * + * Code for Loading the EditresBlock resource. + * + ************************************************************/ + +/* Function Name: CvStringToBlock + * Description: Converts a string to an editres block value. + * Arguments: dpy - the display. + * args, num_args - **UNUSED ** + * from_val, to_val - value to convert, and where to put result + * converter_data - ** UNUSED ** + * Returns: TRUE if conversion was sucessful. + */ + +/* ARGSUSED */ +static Boolean +CvtStringToBlock(dpy, args, num_args, from_val, to_val, converter_data) +Display * dpy; +XrmValue * args; +Cardinal * num_args; +XrmValue * from_val, * to_val; +XtPointer * converter_data; +{ + char ptr[BUFSIZ]; + static EditresBlock block; + +/* XmuCopyISOLatin1Lowered(ptr, from_val->addr);*/ + + + if (streq(ptr, "none")) + block = BlockNone; + else if (streq(ptr, "setvalues")) + block = BlockSetValues; + else if (streq(ptr, "all")) + block = BlockAll; + else { + Cardinal num_params = 1; + String params[1]; + + params[0] = from_val->addr; + XtAppWarningMsg(XtDisplayToApplicationContext(dpy), + "CvtStringToBlock", "unknownValue", "EditresError", + "Could not convert string \"%s\" to EditresBlock.", + params, &num_params); + return(FALSE); + } + + if (to_val->addr != NULL) { + if (to_val->size < sizeof(EditresBlock)) { + to_val->size = sizeof(EditresBlock); + return(FALSE); + } + *(EditresBlock *)(to_val->addr) = block; + } + else + to_val->addr = (XtPointer) block; + + to_val->size = sizeof(EditresBlock); + return(TRUE); +} + +#define XtREditresBlock ("EditresBlock") + +/* Function Name: LoadResources + * Description: Loads a global resource the determines of this + * application should allow Editres requests. + * Arguments: w - any widget in the tree. + * Returns: none. + */ + +static void +LoadResources(w) +Widget w; +{ + static XtResource resources[] = { + {"editresBlock", "EditresBlock", XtREditresBlock, sizeof(EditresBlock), + XtOffsetOf(Globals, block), XtRImmediate, (XtPointer) BlockNone} + }; + + for (; XtParent(w) != NULL; w = XtParent(w)) {} + + XtAppSetTypeConverter(XtWidgetToApplicationContext(w), + XtRString, XtREditresBlock, CvtStringToBlock, + NULL, (Cardinal) 0, XtCacheAll, NULL); + + XtGetApplicationResources( w, (caddr_t) &globals, resources, + XtNumber(resources), NULL, (Cardinal) 0); +} + + diff --git a/RegEdit.h b/RegEdit.h new file mode 100644 index 0000000..5274151 --- /dev/null +++ b/RegEdit.h @@ -0,0 +1,65 @@ +/* $XConsortium: RegEdit.h /main/5 1995/07/15 20:44:09 drk $ */ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ + + /* Ensure that the file be included only once. */ +#ifndef _XmdRegEdit_h +#define _XmdRegEdit_h + +#include + +/* Allow for C++ compilation. */ +#ifdef __cplusplus +extern "C" { +#endif + + +extern void XmdRegisterEditres(Widget toplevel); + +/* Allow for C++ compilation. */ +#ifdef __cplusplus +} /* Close scope of 'extern "C"' declaration which encloses file. */ +#endif + + +/* Ensure that the file be included only once. */ +#endif /* _XmdRegEdit_h */ +/* DON'T ADD ANYTHING AFTER THIS #endif */ + diff --git a/RegEditI.h b/RegEditI.h new file mode 100644 index 0000000..4608593 --- /dev/null +++ b/RegEditI.h @@ -0,0 +1,368 @@ +/* + * @OPENGROUP_COPYRIGHT@ + * COPYRIGHT NOTICE + * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. + * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group + * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for + * the full copyright text. + * + * This software is subject to an open license. It may only be + * used on, with or for operating systems which are themselves open + * source systems. You must contact The Open Group for a license + * allowing distribution and sublicensing of this software on, with, + * or for operating systems which are not Open Source programs. + * + * See http://www.opengroup.org/openmotif/license for full + * details of the license agreement. Any use, reproduction, or + * distribution of the program constitutes recipient's acceptance of + * this agreement. + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY + * OR FITNESS FOR A PARTICULAR PURPOSE + * + * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT + * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + */ +/* + * HISTORY + */ + + +/* $XConsortium: RegEditI.h /main/4 1995/07/14 10:05:01 drk $ */ + +/* + +Copyright (c) 1989 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +/* + * Author: Chris D. Peterson, MIT X Consortium + */ + +/************************************************************ + + The Editres Protocol + + + The Client message sent to the application is: + + ATOM = "ResEditor" --- RES_EDITOR_NAME + + FORMAT = 32 --- RES_EDIT_SEND_EVENT_FORMAT + + l[0] = timestamp + l[1] = command atom name + l[2] = ident of command. + l[3] = protocol version number to use. + + + + The binary protocol has the following format: + + Card8: 8-bit unsingned integer + Card16: 16-bit unsingned integer + Card32: 32-bit unsingned integer + Int16: 16-bit signed integer + Window: 32-bit value + Widget: 32-bit value + String8: ListOfCard8 + + [a][b][c] represent an exclusive list of choices. + + All widgets are passed as a list of widgets, containing the + full instance heirarch of this widget. The hierarchy is ordered + from parent to child. Thus the first element of each list is + the root of the widget tree (this makes verifying that the widget + still exists, MUCH faster). + + ListOfFoo comprises a list of things in the following format: + + number: Card16 + things: ???? + + This is a synchronous protocol, every request MUST be followed by a + reply. + + Request: + + Serial Number: Card8 + Op Code: Card8 - { SendWidgetTree = 0, + SetValues = 1, + GetResources = 2, + GetGeometry = 3, + FindChild = 4, + GetValues = 5 } + Length: Card32 + Data: + + Reply: + + Serial Number: Card8 + Type: Card8 - { Formatted = 0, + Unformatted = 1, + ProtocolMismatch = 2 + } + Length: Card32 + + + Byte Order: + + All Fields are MSB -> LSB + + Data: + + Formatted: + + The data contains the reply information for the request as + specified below if the reply type is "Formatted". The return + values for the other reply types are shown below. + + Unformatted: + + Message: String8 + + ProtocolMismatch: + + RequestedVersion: Card8 + +------------------------------------------------------------ + + SendWidgetTree: + + ---> + + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + name: String8 + class: String8 + window: Card32 + toolkit: String8 + + Send Widget Tree returns the toolkit type, and a fuly specified list + of widgets for each widget in the tree. This is enough information + to completely reconstruct the entire widget heirarchy. + + The window return value contains the Xid of the window currently + used by this widget. If the widget is unrealized then 0 is returned, + and if widget is a non-windowed object a value of 2 is returned. + + SetValues: + + name: String8 + type: String8 + value: String8 + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + + ---> + + Number of Entries: Card16 + Entry: + widget: ListOfWidgets + message: String8 + + SetValues will allow the same resource to be set on a number of + widgets. This function will return an error message if the SetValues + request caused an Xt error. + + GetValues: + + names: ListOfString8 + widget: Widget + + ---> + novalues: ListOfCard16 + values: ListOfString8 + + GetValues will allow a number of resource values to be read + on a particular widget. The request specifies the names of + the resources wanted and the widget id these resources are + from. The reply returns a list of indices from the requests + name list of resources for which a value can not be returned. + It also returns a list of returned values, in the order of the + requests names list, skipping those indices present in novalues. + + GetResources: + + Number of Entries: Card16 + Entry + widget: ListOfWidgets: + + ----> + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + Error: Bool + + [ Message: String 8 ] + [ Number of Resources: Card16 + Resource: + Kind: {normal, constraint} + Name: String8 + Class: String8 + Type: String8 ] + + GetResource retrieves the kind, name, class and type for every + widget passed to it. If an error occured with the resource fetch + Error will be set to True for the given widget and a message + is returned rather than the resource info. + + GetGeometry: + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + + ----> + + Number of Entries: Card16 + Entry + Widget: ListOfWidgets: + Error: Bool + + [ message: String 8 ] + [ mapped: Boolean + X: Int16 + Y: Int16 + Width: Card16 + Height: Card16 + BorderWidth: Card16 ] + + GetGeometry retreives the mapping state, x, y, width, height + and border width for each widget specified. If an error occured + with the geometry fetch "Error" will be set to True for the given + widget and a message is returned rather than the geometry info. + X an Y corrospond to the root coordinates of the upper left corner + of the widget (outside the window border). + + FindChild: + + Widget: ListOfWidgets + X: Int16 + Y: Int16 + + ---> + + Widget: ListOfWidgets + + Find Child returns a descendent of the widget specified that + is at the root coordinates specified. + + NOTE: + + The returned widget is undefined if the point is contained in + two or more mapped widgets, or in two overlapping Rect objs. + + GetValues: + + names: ListOfString8 + widget: Widget + + ---> + + values: ListOfString8 + + GetValues will allow a number of resource values to be read + on a particular widget. Currently only InterViews 3.0.1 Styles + and their attributes are supported. In addition, the current + user interface only supports the return of 1 resource. The ability + to specify and return multiple resources is defined for future editres + interfaces where some or all of a widgets resource values are returned + and displayed at once. + + +************************************************************/ + +#include +#include + +#define XER_NBBY 8 /* number of bits in a byte */ +#define BYTE_MASK 255 + +#define HEADER_SIZE 6 + +#define EDITRES_IS_OBJECT 2 +#define EDITRES_IS_UNREALIZED 0 + +/* + * Format for atoms. + */ + +#define EDITRES_FORMAT 8 +#define EDITRES_SEND_EVENT_FORMAT 32 + +/* + * Atoms + */ + +#define EDITRES_NAME "Editres" +#define EDITRES_COMMAND_ATOM "EditresCommand" +#define EDITRES_COMM_ATOM "EditresComm" +#define EDITRES_CLIENT_VALUE "EditresClientVal" +#define EDITRES_PROTOCOL_ATOM "EditresProtocol" + +typedef enum { SendWidgetTree = 0, + SetValues = 1, + GetResources = 2, + GetGeometry = 3, + FindChild = 4, + GetValues = 5 + } EditresCommand; + +typedef enum {NormalResource = 0, ConstraintResource = 1} ResourceType; + +/* + * The type of a resource identifier. + */ + +typedef unsigned char ResIdent; + +typedef enum {PartialSuccess= 0, Failure= 1, ProtocolMismatch= 2} EditResError; + +typedef struct _WidgetInfo { + unsigned short num_widgets; + unsigned long * ids; + Widget real_widget; +} WidgetInfo; + +typedef struct _ProtocolStream { + unsigned long size, alloc; + unsigned char *real_top, *top, *current; +} ProtocolStream; + diff --git a/autojuke.c b/autojuke.c new file mode 100644 index 0000000..c27f135 --- /dev/null +++ b/autojuke.c @@ -0,0 +1,344 @@ +/* + * program "map-file" for linux autofs + * + * usage: /usr/sbin/automount /jukebox program /usr/sbin/autojuke + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "chio.h" + +#define PROG "autojuke" +#define CHANGER "/dev/sch0" +#define CONFIG "/etc/autojuke.conf" + +#define DEBUG 0 /* set to 1 for debug output */ + +/* ---------------------------------------------------------------------- */ + +int +check_mtab(int n, char **devs) +{ + int i,res; + FILE *fp; + struct stat st; + char line[79],dev[32]; + int *fl; + + /* check for lock file */ + for (i = 0; i < 5; i++) { + if (-1 == stat("/etc/mtab~",&st)) + break; + sleep(1); + } + if (5 == i) { + fprintf(stderr,PROG ": /etc/mtab locked\n"); + exit(1); + } + + if (NULL == (fp = fopen("/etc/mtab", "r"))) { + perror(PROG ": open /etc/mtab"); + exit(1); + } + fl = malloc(n*sizeof(int)); + memset(fl,0,n*sizeof(int)); + while (NULL != fgets(line,79,fp)) { + sscanf(line," %31s",dev); + for (i = 0; i < n; i++) { + if (0 == strcmp(dev,devs[i])) + fl[i] = 1; + } + } + res = -1; + for (i = 0; i < n; i++) { + if (0 == fl[i]) + res = i; + } + free(fl); + fclose(fp); + return res; +} + +static int cdrom_tray(char *dev, int eject) +{ + int fd = -1; + + if (-1 == (fd = open(dev,O_RDONLY | O_NONBLOCK))) + goto err; + if (eject) { + if (-1 == ioctl(fd,CDROMEJECT,NULL)) + goto err; + } else { + if (-1 == ioctl(fd,CDROMCLOSETRAY,NULL)) + goto err; + } + close(fd); + return 0; + + err: + if (-1 != fd) + close(fd); + return -1; +} + +int +main(int argc, char *argv[]) +{ + struct changer_params params; + struct changer_get_element st; + struct changer_get_element *dt; + struct changer_move move; + char **devs,line[80],mopt[80],*h; + int fd,i,slot,drive; + int try_umount = 0; + int eject_cdrom = 0; + FILE *fp; + + if (argc != 2) { + fprintf(stderr,"Read the f*** manual!\n\n*plonk*\n\n"); + exit(1); + } + + if (-1 == (fd = open(CHANGER,O_RDONLY))) { + perror(PROG ": open " CHANGER); + exit(1); + } + + /* read changer status information */ + if (ioctl(fd,CHIOGPARAMS,¶ms)) { + perror(PROG ": ioctl CHIOGPARAMS"); + exit(1); + } +#if DEBUG + fprintf(stderr,PROG ": %d drives, %d slots\n", + params.cp_ndrives,params.cp_nslots); +#endif + + dt = malloc(sizeof(struct changer_get_element)*params.cp_ndrives); + for (i = 0; i < params.cp_ndrives; i++) { + dt[i].cge_type=CHET_DT; + dt[i].cge_unit=i; + if (ioctl(fd,CHIOGELEM,dt+i)) { + fprintf(stderr,PROG ":ioctl CHIOGELEM mt %d: %s\n", + i,strerror(errno)); + exit(1); + } + if (NULL != (h = strchr(dt[i].cge_pvoltag,' '))) *h = '\0'; + if (NULL != (h = strchr(dt[i].cge_avoltag,' '))) *h = '\0'; + } + + /* read config file */ + if (NULL == (fp = fopen(CONFIG, "r"))) { + perror(PROG ": open " CONFIG); + exit(1); + } + devs = malloc(sizeof(char*)*params.cp_ndrives); + for (i = 0; NULL != fgets(line,79,fp);) { + if (line[0] == '#' || line[0] == '\n') + continue; + if (sscanf(line,"try_umount= %d",&try_umount)) + continue; + if (sscanf(line,"eject_cdrom= %d",&eject_cdrom)) + continue; + if (line==strstr(line,"mopt=")) { + strncpy(mopt,line+5,79); + mopt[strlen(mopt)-1]='\0'; + continue; + } + + devs[i] = malloc(32); + if (!sscanf(line,"dev= %31s",devs[i])) { + fprintf(stderr,PROG ": config syntax error: %s",line); + exit(1); + } +#if DEBUG + fprintf(stderr,PROG ": drive %d: %s\n",i,devs[i]); +#endif + i++; + } + fclose(fp); + if (i != params.cp_ndrives) { + fprintf(stderr, + PROG ": number of drives (%d) does'nt match the\n" + PROG ": number of dev=... lines (%d) in " CONFIG "\n", + params.cp_ndrives,i); + exit(1); + } + + /* everything fine so far, lets check what key we got... */ + for (i = 0; argv[1][i];i++) + if (!isdigit(argv[1][i])) + break; + if (!argv[1][i]) { + /* we got a number */ + slot = atoi(argv[1]); + if (slot < 0 || slot >= params.cp_nslots) { + fprintf(stderr,PROG ": slot %d is out of range\n",slot); + exit(1); + } + + /* check drives */ + for (drive = -1, i = 0; i < params.cp_ndrives; i++) { + if ((dt[i].cge_status & CESTATUS_FULL) && + (dt[i].cge_flags & CGE_SRC) && + (dt[i].cge_srctype == CHET_ST) && + (dt[i].cge_srcunit == slot)) { + drive = i; + break; + } + } + + if (-1 != drive) { + /* Ha! requested media is still in a drive. Nothing to do :-) */ + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + } + + st.cge_type=CHET_ST; + st.cge_unit=slot; + if (ioctl(fd,CHIOGELEM,&st)) { + fprintf(stderr,PROG ": ioctl CHIOGELEM st %d: %s\n", + slot,strerror(errno)); + exit(1); + } + + if (!(st.cge_status & CESTATUS_FULL)) { + fprintf(stderr,PROG ": slot %d has no media\n",slot); + exit(1); + } + } else { + + /* no number -- assume volume tag */ + + /* check drives */ + for (drive = -1, i = 0; i < params.cp_ndrives; i++) { + if ((dt[i].cge_status & CESTATUS_FULL) && + (dt[i].cge_flags & CGE_PVOLTAG) && + (0 == strcmp(argv[1],dt[i].cge_pvoltag))) { + drive = i; + break; + } + } + + if (-1 != drive) { + /* Ha! requested media is still in a drive. Nothing to do :-) */ + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + } + + /* Find the tag */ + st.cge_type=CHET_ST; + for (slot = -1, i = 0; i < params.cp_nslots; i++) { + st.cge_unit=i; + if (ioctl(fd,CHIOGELEM,&st)) { + fprintf(stderr,PROG ": ioctl CHIOGELEM st %d: %s\n", + i,strerror(errno)); + exit(1); + } + + if (NULL != (h = strchr(st.cge_pvoltag,' '))) *h = '\0'; + if (NULL != (h = strchr(st.cge_avoltag,' '))) *h = '\0'; + + if ((st.cge_status & CESTATUS_FULL) && + (st.cge_flags & CGE_PVOLTAG) && + (0 == strcmp(argv[1],st.cge_pvoltag))) { + slot = i; + break; + } + } + + if (-1 == slot) { + fprintf(stderr,PROG ": volume tag %s not found\n",argv[1]); + exit(1); + } + } + + /* ok, requested media found, check where to put it now */ + + /* look for a empty drive first... */ + for (i = 0, drive = -1; i < params.cp_ndrives; i++) { + if (!(dt[i].cge_status & CESTATUS_FULL)) { + drive = i; + break; + } + } + + if (-1 == drive) + /* failing that, look for a unmounted drive */ + drive = check_mtab(params.cp_ndrives,devs); + + if (-1 == drive && try_umount) { + /* still no success -- tell automount to umount idle mount-points */ +#if DEBUG + fprintf(stderr,PROG ": sending SIGUSR1 to automount\n"); +#endif + kill(SIGUSR1,getppid()); + sleep(1); /* XXX */ + drive = check_mtab(params.cp_ndrives,devs); + } + + if (-1 == drive) { + /* bad luck */ + fprintf(stderr,PROG ": all drives busy, sorry\n"); + exit(1); + } + + if (eject_cdrom) { + if (0 != cdrom_tray(devs[drive],1)) + exit(1); + } + + if (dt[drive].cge_status & CESTATUS_FULL) { + /* Hmm, full. Ok, put back the old media ... */ + if (!(dt[drive].cge_flags & CGE_SRC)) { + fprintf(stderr,PROG ": source element for drive %d unknown\n", + drive); + goto close_error_exit; + } + memset(&move,0,sizeof(struct changer_move)); + move.cm_totype = dt[drive].cge_srctype; + move.cm_tounit = dt[drive].cge_srcunit; + move.cm_fromtype = CHET_DT; + move.cm_fromunit = drive; + if (ioctl(fd,CHIOMOVE,&move)) { + fprintf(stderr,PROG ": ioctl MOVE (unload): %s\n", + strerror(errno)); + goto close_error_exit; + } + } + + memset(&move,0,sizeof(struct changer_move)); + move.cm_totype = CHET_DT; + move.cm_tounit = drive; + move.cm_fromtype = CHET_ST; + move.cm_fromunit = slot; + if (ioctl(fd,CHIOMOVE,&move)) { + fprintf(stderr,PROG ": ioctl MOVE (load): %s\n", + strerror(errno)); + goto close_error_exit; + } + + if (eject_cdrom) + cdrom_tray(devs[drive],0); + + printf("%s\t:%s\n",mopt,devs[drive]); + exit(0); + + close_error_exit: + if (eject_cdrom) + cdrom_tray(devs[drive],0); + exit(1); +} diff --git a/autojuke.conf b/autojuke.conf new file mode 100644 index 0000000..d60cf28 --- /dev/null +++ b/autojuke.conf @@ -0,0 +1,8 @@ +# bool (0/1): try to umount by sending SIGUSR1 to automount +try_umount=1 + +# mount options for autofs, see autofs(5) +mopt=-fstype=auto + +# one line per device +dev=/dev/??? diff --git a/autojuke.man b/autojuke.man new file mode 100644 index 0000000..8df8208 --- /dev/null +++ b/autojuke.man @@ -0,0 +1,60 @@ +.TH autojuke 8 "(c) 1997-2001 Gerd Knorr" +.SH NAME +autojuke - a tool to access jukeboxes with autofs +.SH SYNOPSIS +/usr/sbin/automount /jukebox program /usr/sbin/autojuke +.SH DESCRIPTION +.B autojuke +is a "program map" (in autofs speak). You have to create a config +file (/etc/autojuke.conf) for it first (or copy & edit the sample +file), see below for a description of the syntax. +.P +After starting autofs you should be able to change & mount media just +by accessing /jukebox/ or by accessing +/jukebox/. Error messages show up in the syslog +(/var/log/messages). +.SH CONFIG FILE +Syntax is very simple: The config file has just some "variable = value" +lines. These are the variables: +.TP +.B try_umount=0|1 +It is possible to signal the automound daemon (with SIGUSR1) it should +umount idle devices. If you set this to 1, autojuke will try this if +there is no idle drive. You probably want to use either this feature +or a short timeout value for automount. +.TP +.B mopts=... +Specify mount options (fs type for example), autojuke passes them to +the automount daemon. Check out the automount manpage for details. +.TP +.B dev=special file +specifies the special file(s) (/dev/something) of the data transfer +elements. There must be exactly one line per data transfer element. +autojuke uses this to look for idle drives (using /etc/mtab) and to +tell automount which device it should mount. Order is important, the +first line should address "d0", etc... +.SH NOTES +Volume tags work only if the changer supports them. +Double-sided media is not supported (yet). +.P +For CD-ROM's it is faster and cheaper these days if you copy images to +a big hard disk and mount the images using the loop device. This also +allows easy parallel access to different CD's at the same time. +.SH AUTHOR +Gerd Knorr +.SH COPYRIGHT +Copyright (C) 2000 Gerd Knorr +.P +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +.P +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/chio.h b/chio.h new file mode 100644 index 0000000..b08e8c2 --- /dev/null +++ b/chio.h @@ -0,0 +1,160 @@ +/* + * ioctl interface for the scsi media changer driver + */ + +/* changer element types */ +#define CHET_MT 0 /* media transport element (robot) */ +#define CHET_ST 1 /* storage element (media slots) */ +#define CHET_IE 2 /* import/export element */ +#define CHET_DT 3 /* data transfer element (tape/cdrom/whatever) */ +#define CHET_V1 4 /* vendor specific #1 */ +#define CHET_V2 5 /* vendor specific #2 */ +#define CHET_V3 6 /* vendor specific #3 */ +#define CHET_V4 7 /* vendor specific #4 */ + + +/* + * CHIOGPARAMS + * query changer properties + * + * CHIOVGPARAMS + * query vendor-specific element types + * + * accessing elements works by specifing type and unit of the element. + * for eample, storage elements are addressed with type = CHET_ST and + * unit = 0 .. cp_nslots-1 + * + */ +struct changer_params { + int cp_curpicker; /* current transport element */ + int cp_npickers; /* number of transport elements (CHET_MT) */ + int cp_nslots; /* number of storage elements (CHET_ST) */ + int cp_nportals; /* number of import/export elements (CHET_IE) */ + int cp_ndrives; /* number of data transfer elements (CHET_DT) */ +}; +struct changer_vendor_params { + int cvp_n1; /* number of vendor specific elems (CHET_V1) */ + char cvp_label1[16]; + int cvp_n2; /* number of vendor specific elems (CHET_V2) */ + char cvp_label2[16]; + int cvp_n3; /* number of vendor specific elems (CHET_V3) */ + char cvp_label3[16]; + int cvp_n4; /* number of vendor specific elems (CHET_V4) */ + char cvp_label4[16]; + int reserved[8]; +}; + + +/* + * CHIOMOVE + * move a medium from one element to another + */ +struct changer_move { + int cm_fromtype; /* type/unit of source element */ + int cm_fromunit; + int cm_totype; /* type/unit of destination element */ + int cm_tounit; + int cm_flags; +}; +#define CM_INVERT 1 /* flag: rotate media (for double-sided like MOD) */ + + +/* + * CHIOEXCHANGE + * move one medium from element #1 to element #2, + * and another one from element #2 to element #3. + * element #1 and #3 are allowed to be identical. + */ +struct changer_exchange { + int ce_srctype; /* type/unit of element #1 */ + int ce_srcunit; + int ce_fdsttype; /* type/unit of element #2 */ + int ce_fdstunit; + int ce_sdsttype; /* type/unit of element #3 */ + int ce_sdstunit; + int ce_flags; +}; +#define CE_INVERT1 1 +#define CE_INVERT2 2 + + +/* + * CHIOPOSITION + * move the transport element (robot arm) to a specific element. + */ +struct changer_position { + int cp_type; + int cp_unit; + int cp_flags; +}; +#define CP_INVERT 1 + + +/* + * CHIOGSTATUS + * get element status for all elements of a specific type + */ +struct changer_element_status { + int ces_type; + unsigned char *ces_data; +}; +#define CESTATUS_FULL 0x01 /* full */ +#define CESTATUS_IMPEXP 0x02 /* media was imported (inserted by sysop) */ +#define CESTATUS_EXCEPT 0x04 /* error condition */ +#define CESTATUS_ACCESS 0x08 /* access allowed */ +#define CESTATUS_EXENAB 0x10 /* element can export media */ +#define CESTATUS_INENAB 0x20 /* element can import media */ + + +/* + * CHIOGELEM + * get more detailed status informtion for a single element + */ +struct changer_get_element { + int cge_type; /* type/unit */ + int cge_unit; + int cge_status; /* status */ + int cge_errno; /* errno */ + int cge_srctype; /* source element of the last move/exchange */ + int cge_srcunit; + int cge_id; /* scsi id (for data transfer elements) */ + int cge_lun; /* scsi lun (for data transfer elements) */ + char cge_pvoltag[36]; /* primary volume tag */ + char cge_avoltag[36]; /* alternate volume tag */ + int cge_flags; +}; +/* flags */ +#define CGE_ERRNO 0x01 /* errno available */ +#define CGE_INVERT 0x02 /* media inverted */ +#define CGE_SRC 0x04 /* media src available */ +#define CGE_IDLUN 0x08 /* ID+LUN available */ +#define CGE_PVOLTAG 0x10 /* primary volume tag available */ +#define CGE_AVOLTAG 0x20 /* alternate volume tag available */ + + +/* + * CHIOSVOLTAG + * set volume tag + */ +struct changer_set_voltag { + int csv_type; /* type/unit */ + int csv_unit; + char csv_voltag[36]; /* volume tag */ + int csv_flags; +}; +#define CSV_PVOLTAG 0x01 /* primary volume tag */ +#define CSV_AVOLTAG 0x02 /* alternate volume tag */ +#define CSV_CLEARTAG 0x04 /* clear volume tag */ + +/* ioctls */ +#define CHIOMOVE _IOW('c', 1,struct changer_move) +#define CHIOEXCHANGE _IOW('c', 2,struct changer_exchange) +#define CHIOPOSITION _IOW('c', 3,struct changer_position) +#define CHIOGPICKER _IOR('c', 4,int) /* not impl. */ +#define CHIOSPICKER _IOW('c', 5,int) /* not impl. */ +#define CHIOGPARAMS _IOR('c', 6,struct changer_params) +#define CHIOGSTATUS _IOW('c', 8,struct changer_element_status) +#define CHIOGELEM _IOW('c',16,struct changer_get_element) +#define CHIOINITELEM _IO('c',17) +#define CHIOSVOLTAG _IOW('c',18,struct changer_set_voltag) +#define CHIOGVPARAMS _IOR('c',19,struct changer_vendor_params) diff --git a/list.h b/list.h new file mode 100644 index 0000000..10a0518 --- /dev/null +++ b/list.h @@ -0,0 +1,166 @@ +/* + * Simple doubly linked list implementation. + * -- shameless stolen from the linux kernel sources + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev - iterate over a list in reverse order + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \ + pos = pos->prev, prefetch(pos->prev)) + diff --git a/man.c b/man.c new file mode 100644 index 0000000..4862b7f --- /dev/null +++ b/man.c @@ -0,0 +1,116 @@ +/* + * motif-based man page renderer + * (c) 2001 Gerd Knorr + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RegEdit.h" +#include "man.h" + +/*----------------------------------------------------------------------*/ + +#define MAN_UNDEF 0 +#define MAN_NORMAL 1 +#define MAN_BOLD 2 +#define MAN_UNDERLINE 3 + +static XmStringTag man_tags[] = { + NULL, NULL, "bold", "underline" +}; + +static void +man_destroy(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XtDestroyWidget(clientdata); +} + +void +man(Widget parent, char *page) +{ + Widget dlg,view,label; + XmString xmpage,xmchunk; + char line[1024],chunk[256]; + int s,d,cur,last; + FILE *fp; + + /* build dialog */ + dlg = XmCreatePromptDialog(parent,"man",NULL,0); + XmdRegisterEditres(XtParent(dlg)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_SELECTION_LABEL)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_CANCEL_BUTTON)); + XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_TEXT)); + XtAddCallback(dlg,XmNokCallback,man_destroy,dlg); + view = XmCreateScrolledWindow(dlg,"view",NULL,0); + XtManageChild(view); + label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,view, NULL); + XtManageChild(dlg); + + /* fetch page and render into XmString */ + sprintf(line,"man %s 2>/dev/null",page); + fp = popen(line,"r"); + xmpage = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT, NULL); + while (NULL != fgets(line,sizeof(line)-1,fp)) { + last = MAN_UNDEF; + for (s = 0, d = 0; line[s] != '\0';) { + /* check current char */ + cur = MAN_NORMAL; + if (line[s+1] == '\010' && line[s] == line[s+2]) + cur = MAN_BOLD; + if (line[s] == '_' && line[s+1] == '\010') + cur = MAN_UNDERLINE; + /* add chunk if completed */ + if (MAN_UNDEF != last && cur != last) { + xmchunk = XmStringGenerate(chunk,NULL,XmMULTIBYTE_TEXT, + man_tags[last]); + xmpage = XmStringConcatAndFree(xmpage,xmchunk); + d = 0; + } + /* add char to chunk */ + switch (cur) { + case MAN_BOLD: + case MAN_UNDERLINE: + s += 2; + case MAN_NORMAL: + chunk[d++] = line[s++]; + break; + } + chunk[d] = '\0'; + last = cur; + } + /* add last chunk for line */ + xmchunk = XmStringGenerate(chunk, NULL, XmMULTIBYTE_TEXT, + man_tags[last]); + xmpage = XmStringConcatAndFree(xmpage,xmchunk); + } + XtVaSetValues(label,XmNlabelString,xmpage,NULL); + XmStringFree(xmpage); + fclose(fp); +} + +void +man_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + char *page = clientdata; + man(widget,page); +} + +void +man_action(Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + if (*num_params > 0) + man(widget,params[0]); +} diff --git a/man.h b/man.h new file mode 100644 index 0000000..3a04d93 --- /dev/null +++ b/man.h @@ -0,0 +1,4 @@ +void man(Widget parent, char *page); +void man_cb(Widget widget, XtPointer clientdata, XtPointer call_data); +void man_action(Widget widget, XEvent *event, + String *params, Cardinal *num_params); diff --git a/mover.c b/mover.c new file mode 100644 index 0000000..8866abe --- /dev/null +++ b/mover.c @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "chio.h" + +#define CHANGER "/dev/sch0" + +/* ---------------------------------------------------------------------- */ + +struct changer_params params; +struct changer_vendor_params vparams; + +int +parse_arg(char *arg, int *type, int *nr) +{ + char t; + + if (NULL == arg) + return -1; + if (2 != sscanf(arg,"%c%i",&t,nr)) + return -1; + switch (t) { + case 'm': + case 'M': *type = CHET_MT; break; + case 's': + case 'S': *type = CHET_ST; break; + case 'e': + case 'E': *type = CHET_IE; break; + case 'd': + case 'D': *type = CHET_DT; break; + case 'v': + case 'V': *type = CHET_V1; break; + case 'w': + case 'W': *type = CHET_V2; break; + case 'x': + case 'X': *type = CHET_V3; break; + case 'y': + case 'Y': *type = CHET_V4; break; + default: + return -1; + } + return 0; +} + +static void +print_stat(int fd, int type, int count) +{ + struct changer_element_status ces; + struct changer_get_element elinfo; + int i,rc; + + switch (type) { + case CHET_MT: printf("medium transport\n"); break; + case CHET_ST: printf("storage\n"); break; + case CHET_IE: printf("import/export\n"); break; + case CHET_DT: printf("data transfer\n"); break; + case CHET_V1: + printf("vendor specific #1 [%s]\n",vparams.cvp_label1); + break; + case CHET_V2: + printf("vendor specific #2 [%s]\n",vparams.cvp_label2); + break; + case CHET_V3: + printf("vendor specific #3 [%s]\n",vparams.cvp_label3); + break; + case CHET_V4: + printf("vendor specific #4 [%s]\n",vparams.cvp_label4); + break; + } + + ces.ces_type = type; + ces.ces_data = malloc(count); + rc = ioctl(fd,CHIOGSTATUS,&ces); + if (rc) { + fprintf(stderr,"ioctl failed (GSTATUS): %s\n",strerror(errno)); + exit(1); + } + for (i = 0; i < count; i++) { + printf(" %2d: ",i); + if (ces.ces_data[i] & CESTATUS_INENAB) printf("inenab "); + if (ces.ces_data[i] & CESTATUS_EXENAB) printf("exenab "); + if (ces.ces_data[i] & CESTATUS_ACCESS) printf("access "); + if (ces.ces_data[i] & CESTATUS_EXCEPT) printf("except "); + if (ces.ces_data[i] & CESTATUS_IMPEXP) printf("impexp "); + if (ces.ces_data[i] & CESTATUS_FULL) printf("full "); + + memset(&elinfo,0,sizeof(elinfo)); + elinfo.cge_type = type; + elinfo.cge_unit = i; + + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (GELEM): %s\n",strerror(errno)); + exit(1); + } + + if (elinfo.cge_flags & CGE_PVOLTAG && strlen(elinfo.cge_pvoltag) > 0 ) { + printf("\ttag: '%s'",elinfo.cge_pvoltag); + } else + if (ces.ces_data[i] & CESTATUS_FULL) + printf("\t(no volume tag)"); + + + printf("\n"); + } + free(ces.ces_data); +} + +/* ---------------------------------------------------------------------- */ + +int +main(int argc, char *argv[]) +{ + int fd,rc; + + if (-1 == (fd = open(CHANGER,O_RDONLY))) { + perror("open"); + exit(1); + } + + rc = ioctl(fd,CHIOGPARAMS,¶ms); + if (rc) { + fprintf(stderr,"ioctl failed (GPARAMS): %s\n",strerror(errno)); + exit(1); + } else { + printf(CHANGER ": mt=%i st=%i ie=%i dt=%i\n", + params.cp_npickers,params.cp_nslots, + params.cp_nportals,params.cp_ndrives); + } + rc = ioctl(fd,CHIOGVPARAMS,&vparams); + if (rc) { + fprintf(stderr,"ioctl failed (VPARAMS): %s\n",strerror(errno)); + } else { + if (vparams.cvp_n1) + printf(CHANGER ": v1=%d [%s]\n",vparams.cvp_n1,vparams.cvp_label1); + if (vparams.cvp_n2) + printf(CHANGER ": v2=%d [%s]\n",vparams.cvp_n2,vparams.cvp_label2); + if (vparams.cvp_n3) + printf(CHANGER ": v3=%d [%s]\n",vparams.cvp_n3,vparams.cvp_label3); + if (vparams.cvp_n4) + printf(CHANGER ": v4=%d [%s]\n",vparams.cvp_n4,vparams.cvp_label4); + } + + if (argc == 1 || 0 == strcasecmp(argv[1],"status")) { + /* no args, so print some status informations */ + print_stat(fd,CHET_MT,params.cp_npickers); + print_stat(fd,CHET_ST,params.cp_nslots); + print_stat(fd,CHET_IE,params.cp_nportals); + print_stat(fd,CHET_DT,params.cp_ndrives); + if (vparams.cvp_n1) + print_stat(fd,CHET_V1,vparams.cvp_n1); + if (vparams.cvp_n2) + print_stat(fd,CHET_V2,vparams.cvp_n2); + if (vparams.cvp_n3) + print_stat(fd,CHET_V3,vparams.cvp_n3); + if (vparams.cvp_n4) + print_stat(fd,CHET_V4,vparams.cvp_n4); + exit(0); + } + + if (0 == strcasecmp(argv[1],"pos")) { + struct changer_position pos; + + memset(&pos,0,sizeof(pos)); + if (-1 == parse_arg(argv[2],&pos.cp_type,&pos.cp_unit)) { + fprintf(stderr,"slot arg parse error (POSITION)\n"); + exit(1); + } + if (argc > 3) + pos.cp_flags = atoi(argv[3]); + rc = ioctl(fd,CHIOPOSITION,&pos); + if (rc) { + fprintf(stderr,"ioctl failed (POSITION): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"load") || + 0 == strcasecmp(argv[1],"unload")) { + struct changer_move move; + + memset(&move,0,sizeof(move)); + if (0 == strcasecmp(argv[1],"load")) { + move.cm_fromtype = CHET_ST; + move.cm_fromunit = atoi(argv[2]); + move.cm_totype = CHET_DT; + move.cm_tounit = 0; + } else { + if (argc > 2) { + move.cm_totype = CHET_ST; + move.cm_tounit = atoi(argv[2]); + } else { + struct changer_get_element elinfo; + memset(&elinfo,0,sizeof(elinfo)); + elinfo.cge_type = CHET_DT; + elinfo.cge_unit = 0; + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (GELEM): %s\n", + strerror(errno)); + exit(1); + } + if (!(elinfo.cge_flags & CGE_SRC)) { + fprintf(stderr,"element source info not available\n"); + exit(1); + } + move.cm_totype = elinfo.cge_srctype; + move.cm_tounit = elinfo.cge_srcunit; + if (elinfo.cge_flags & CGE_INVERT) + move.cm_flags |= CE_INVERT1; + } + move.cm_fromtype = CHET_DT; + move.cm_fromunit = 0; + } + rc = ioctl(fd,CHIOMOVE,&move); + if (rc) { + fprintf(stderr,"ioctl failed (MOVE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"mv")) { + struct changer_move move; + + memset(&move,0,sizeof(move)); + if (-1 == parse_arg(argv[2],&move.cm_fromtype,&move.cm_fromunit) || + -1 == parse_arg(argv[3],&move.cm_totype, &move.cm_tounit )) { + fprintf(stderr,"slot arg parse error (MOVE)\n"); + exit(1); + } + if (argc > 4) + move.cm_flags = atoi(argv[4]); + rc = ioctl(fd,CHIOMOVE,&move); + if (rc) { + fprintf(stderr,"ioctl failed (MOVE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"ex")) { + struct changer_exchange xchg; + + memset(&xchg,0,sizeof(xchg)); + if (-1 == parse_arg(argv[2],&xchg.ce_srctype, &xchg.ce_srcunit ) || + -1 == parse_arg(argv[3],&xchg.ce_fdsttype,&xchg.ce_fdstunit) || + -1 == parse_arg(argv[4],&xchg.ce_sdsttype,&xchg.ce_sdstunit)) { + fprintf(stderr,"slot arg parse error (EXCHANGE)\n"); + exit(1); + } + if (argc > 5) + xchg.ce_flags = atoi(argv[5]); + rc = ioctl(fd,CHIOEXCHANGE,&xchg); + if (rc) { + fprintf(stderr,"ioctl failed (EXCHANGE): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"info")) { + struct changer_get_element elinfo; + + memset(&elinfo,0,sizeof(elinfo)); + if (-1 == parse_arg(argv[2],&elinfo.cge_type, &elinfo.cge_unit)) { + fprintf(stderr,"slot arg parse error (INFO)\n"); + exit(1); + } + rc = ioctl(fd,CHIOGELEM,&elinfo); + if (rc) { + fprintf(stderr,"ioctl failed (INFO): %s\n", + strerror(errno)); + exit(1); + } + + printf("status : "); + if (elinfo.cge_status & CESTATUS_INENAB) printf("inenab "); + if (elinfo.cge_status & CESTATUS_EXENAB) printf("exenab "); + if (elinfo.cge_status & CESTATUS_ACCESS) printf("access "); + if (elinfo.cge_status & CESTATUS_EXCEPT) printf("except "); + if (elinfo.cge_status & CESTATUS_IMPEXP) printf("impexp "); + if (elinfo.cge_status & CESTATUS_FULL) printf("full "); + printf("\n"); + + if (elinfo.cge_flags & CGE_ERRNO) { + printf("error : %s\n",strerror(elinfo.cge_errno)); + } + if (elinfo.cge_flags & CGE_SRC) { + printf("source : "); + switch (elinfo.cge_srctype) { + case CHET_MT: printf("m"); break; + case CHET_ST: printf("s"); break; + case CHET_IE: printf("e"); break; + case CHET_DT: printf("d"); break; + case CHET_V1: printf("v"); break; + case CHET_V2: printf("w"); break; + case CHET_V3: printf("x"); break; + case CHET_V4: printf("y"); break; + } + printf("%d",elinfo.cge_srcunit); + if (elinfo.cge_flags & CGE_INVERT) + printf(" (rotated)"); + printf("\n"); + } + if (elinfo.cge_flags & CGE_IDLUN) { + printf("scsi id : %d\n",elinfo.cge_id); + printf("scsi lun: %d\n",elinfo.cge_lun); + } + if (elinfo.cge_flags & CGE_PVOLTAG) { + printf("pvoltag : %32.32s\n",elinfo.cge_pvoltag); + } + if (elinfo.cge_flags & CGE_AVOLTAG) { + printf("avoltag : %32.32s\n",elinfo.cge_avoltag); + } + + } else if (0 == strcasecmp(argv[1],"init")) { + rc = ioctl(fd,CHIOINITELEM,NULL); + if (rc) { + fprintf(stderr,"ioctl failed (INITELEM): %s\n", + strerror(errno)); + exit(1); + } + + } else if (0 == strcasecmp(argv[1],"settag") || + 0 == strcasecmp(argv[1],"cleartag")) { + struct changer_set_voltag tag; + + memset(&tag,0,sizeof(tag)); + if (-1 == parse_arg(argv[2],&tag.csv_type,&tag.csv_unit)) { + fprintf(stderr,"slot arg parse error (SVOLTAG)\n"); + exit(1); + } + if (argc <= 3) { + fprintf(stderr,"no type (pri | alt) given\n"); + exit(1); + } + if (0 == strcasecmp(argv[3],"pri")) + tag.csv_flags |= CSV_PVOLTAG; + if (0 == strcasecmp(argv[3],"alt")) + tag.csv_flags |= CSV_AVOLTAG; + if (0 == strcasecmp(argv[1],"cleartag")) { + tag.csv_flags |= CSV_CLEARTAG; + } else { + if (argc <= 4) { + fprintf(stderr,"no volume tag given\n"); + exit(1); + } + strncpy(tag.csv_voltag,argv[4],32); + } + rc = ioctl(fd,CHIOSVOLTAG,&tag); + if (rc) { + fprintf(stderr,"ioctl failed (SVOLTAG): %s\n", + strerror(errno)); + exit(1); + } + + } else { + printf("%s: unknown argument\n",argv[1]); + exit(1); + } + + close(fd); + exit(0); +} diff --git a/mover.man b/mover.man new file mode 100644 index 0000000..6bde895 --- /dev/null +++ b/mover.man @@ -0,0 +1,105 @@ +.TH mover 1 "(c) 1997-2002 Gerd Knorr" +.SH NAME +mover - utility to control scsi media changers +.SH SYNOPSIS +.B mover [ command ] +.SH DESCRIPTION +.B mover +is a command line utility for talking to the scsi media changer +driver. +.P +If you start it without any arguments, it will print the status bits +and volume tags (if available) for each changer slot. The status bits +are: +.TP +.B full +set if the element holds a media +.TP +.B access +set if the changer can or is allowed to access the element. If this +bit is not set for the data transfer element, you'll probably have to +use eject the media first. +.TP +.B except +exception - something went really wrong. +.TP +.B inenab +set if the element can import media +.TP +.B exenab +set if the element can export media +.TP +.B impexp +set if the media in the element has been inserted from outside +.P +The last three are used for import/export elements only. +.SH COMMANDS +.TP +.B mover mv [] +This command moves the media from element to element . + can be 0 (default if not specified) or 1. If it is set to 1, +the media will be rotated. That's important for double-sided media +(some MO changers for example). +.br + and are a letter/number combination. The letter is the +element type (t,s,e or d) and the number is the number of the element, +numbering start with zero. "s4" is slot #5, "d0" the read/write +device, ... Hope you get the idea. +.TP +.B mover ex [] +This command moves media1 from to , and media2 from + to . The scsi standard allows and to +address the same element (i.e. exchange and ). +can have the values 0, 1, 2 and 3 here. "1" will rotate media1, "2" +will rotate media2, "3" will rotate both. +.br +The exchange command is optional for SCSI changer devices, so this +might not work if the hardware does not support it. +.TP +.B mover pos [rotate] +This positions the transport arm in front of element . This +command is optional too. +.TP +.B mover load +Move media from storage element into the first device. This is +just a easy-to-remember shortcut for "mover mv s d0". +.TP +.B mover unload [] +Reverse of load, i.e. it moves the media from the first drive to the +storage slot . The argument is optional, if it is missing +the media will be moved back to the slot where it was taken from. +This depends on a optional feature too, therefore the command might +not work without the slot number. +.TP +.B mover info +prints detailed status informations about a element. +.TP +.B mover init +initialize element status (usually makes the changer scan all +elements, may take some time). +.TP +.B mover settag [ pri | alt ] +set primary / alternate volume tag for to . +.TP +.B mover cleartag [ pri | alt ] +clear primary / alternate volume tag for . +.SH SEE ALSO +xmover(1) +.SH AUTHOR +Gerd Knorr +.SH COPYRIGHT +Copyright (C) 1997-2002 Gerd Knorr +.P +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +.P +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/unload.c b/unload.c new file mode 100644 index 0000000..a1005c2 --- /dev/null +++ b/unload.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "scsi/scsi.h" +#include "scsi/scsi_ioctl.h" + +#define DEFAULT_DEVICE "/dev/sda" + +typedef struct scsi_ioctl_command { + unsigned int inlen; + unsigned int outlen; + unsigned char data[0]; +} Scsi_Ioctl_Command; + + +int +main(int argc, char *argv[]) +{ + int fd; + char *prog, device[256], request[256], *h; + Scsi_Ioctl_Command *sic = (Scsi_Ioctl_Command*)request; + + h = strrchr(argv[0],'/'); + prog = h ? h+1 : argv[0]; + + if (argc > 1) { + strcpy(device,argv[1]); + } else { + strcpy(device,DEFAULT_DEVICE); + } + + if (-1 == (fd = open(device, O_RDONLY))) { + fprintf(stderr,"%s: can't open %s: %s\n",prog,device, + sys_errlist[errno]); + exit(1); + } + + if (-1 == ioctl(fd,SCSI_IOCTL_DOORUNLOCK,sic)) { + perror("ioctl (unlock)"); + exit(1); + } + + memset(request,0,256); + sic->inlen = 0; + sic->outlen = 0; + sic->data[0] = START_STOP; + sic->data[1] = 0; + sic->data[2] = 0; + sic->data[3] = 0; + sic->data[4] = (0 == strcmp(prog,"unload")) ? 2 : 3; + sic->data[5] = 0; + + if (-1 == ioctl(fd,SCSI_IOCTL_SEND_COMMAND,sic)) { + perror("ioctl (eject)"); + exit(1); + } + + close(fd); + exit(0); +} diff --git a/unload.man b/unload.man new file mode 100644 index 0000000..ff23984 --- /dev/null +++ b/unload.man @@ -0,0 +1,27 @@ +.TH unload 8 +.SH NAME +unload - load/unload scsi media +.SH SYNOPSIS +.B load +.B unload +.SH DESCRIPTION +The unload program can be used to unload (eject) and load any +removeable SCSI media. It takes a device as argument, unlocks it and +sends a START STOP command. Without argument it uses the compiled-in +default device (there is a #define in the source to change this it you +want). It unloads if the program name is "unload", and loads else. +.P +unload can eject everything: CD-ROM, MOD, ZIP, whatelse. Even for tape +libraries this is useful: Most (all?) of them load the next tape in +order if you send a unload command to the tape device. +.P +It requires root permissions, as the SCSI_IOCTL_SEND_COMMAND ioctl is +allowed for root only (for obvious reasons). +.SH WARNING +It is a quick & dirty hack. It does no sanity checks. It does not ask +stupid questions. It simply does what you have asked for. You can +eject mounted media with it, so use it with care. You have been warned! +.SH AUTHOR +anonymous :-) +.SH COPYRIGHT +This tool is public domain. diff --git a/xmover.c b/xmover.c new file mode 100644 index 0000000..241f0a7 --- /dev/null +++ b/xmover.c @@ -0,0 +1,777 @@ +/* + * xmover -- X11 frontend for scsi media changers + * + * (c) 2002 Gerd Knorr + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chio.h" + +#include "RegEdit.h" +#include "list.h" +#include "man.h" + +/* --------------------------------------------------------------------- */ + +XtAppContext app_context; + +static String fallback_ressources[] = { +#include "xmover.h" + NULL +}; + +struct ARGS { + char *device; + int help; + int debug; +} args; + +XtResource args_desc[] = { + /* name, class, type, size, offset, default_type, default_addr */ + { + /* Strings */ + "device", + XtCString, XtRString, sizeof(char*), + XtOffset(struct ARGS*,device), + XtRString, "/dev/sch0", + },{ + /* Integer */ + "help", + XtCValue, XtRInt, sizeof(int), + XtOffset(struct ARGS*,help), + XtRString, "0" + },{ + "debug", + XtCValue, XtRInt, sizeof(int), + XtOffset(struct ARGS*,debug), + XtRString, "0" + } +}; +const int args_count = XtNumber(args_desc); + +XrmOptionDescRec opt_desc[] = { + { "-c", "device", XrmoptionSepArg, NULL }, + { "-device", "device", XrmoptionSepArg, NULL }, + + { "-debug", "debug", XrmoptionNoArg, "1" }, + { "-h", "help", XrmoptionNoArg, "1" }, + { "-help", "help", XrmoptionNoArg, "1" }, + { "--help", "help", XrmoptionNoArg, "1" }, +}; +const int opt_count = (sizeof(opt_desc)/sizeof(XrmOptionDescRec)); + +/* prototypes */ +static void elem_convert(Widget, XtPointer, XtPointer); +static void elem_destination(Widget, XtPointer, XtPointer); + +/* --------------------------------------------------------------------- */ +/* atoms */ + +static Atom XA_WM_DELETE_WINDOW; + +static Atom XA_TARGETS; +static Atom _MOTIF_EXPORT_TARGETS; + +static Atom _XMOVER_TYPEUNIT; +static Atom _XMOVER_REFRESH; + +static Atom targets[2]; +static Cardinal ntargets; + +void init_atoms(Display *dpy) +{ + XA_WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + + XA_TARGETS = XInternAtom(dpy, "TARGETS", False); + _MOTIF_EXPORT_TARGETS = XInternAtom(dpy, "_MOTIF_EXPORT_TARGETS", False); + + _XMOVER_TYPEUNIT = XInternAtom(dpy, "_XMOVER_TYPEUNIT", False); + _XMOVER_REFRESH = XInternAtom(dpy, "_XMOVER_REFRESH", False); + + targets[ntargets++] = _XMOVER_TYPEUNIT; + targets[ntargets++] = _XMOVER_REFRESH; +} + +/* ---------------------------------------------------------------------- */ +/* redirect stderr to error dialog box */ + +struct stderr_handler { + Widget box; + XmString str; + int pipe; + XtInputId id; +}; + +static void +stderr_input(XtPointer clientdata, int *src, XtInputId *id) +{ + struct stderr_handler *h = clientdata; + XmString item; + Widget label; + char buf[1024]; + int rc; + + rc = read(h->pipe,buf,sizeof(buf)-1); + if (rc <= 0) { + /* Oops */ + XtRemoveInput(h->id); + close(h->pipe); + XtDestroyWidget(h->box); + free(h); + } + buf[rc] = 0; + item = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL); + h->str = XmStringConcatAndFree(h->str,item); + label = XmMessageBoxGetChild(h->box,XmDIALOG_MESSAGE_LABEL); + XtVaSetValues(label,XmNlabelString,h->str,NULL); + XtManageChild(h->box); +}; + +static void +stderr_ok_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct stderr_handler *h = clientdata; + + XmStringFree(h->str); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); + XtUnmanageChild(h->box); +} + +static void +stderr_close_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct stderr_handler *h = clientdata; + + XmStringFree(h->str); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); +} + +static void +stderr_init(Widget parent) +{ + struct stderr_handler *h; + int p[2]; + + h = malloc(sizeof(*h)); + memset(h,0,sizeof(*h)); + h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL); + h->box = XmCreateErrorDialog(parent,"errbox",NULL,0); + XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_CANCEL_BUTTON)); + XtAddCallback(h->box,XmNokCallback,stderr_ok_cb,h); + XtAddCallback(XtParent(h->box),XmNpopdownCallback,stderr_close_cb,h); + pipe(p); + dup2(p[1],2); + close(p[1]); + h->pipe = p[0]; + h->id = XtAppAddInput(app_context,h->pipe,(XtPointer)XtInputReadMask, + stderr_input,h); +} + +/* ---------------------------------------------------------------------- */ +/* pointer games */ + +struct ptr_list { + struct list_head list; + Widget widget; +}; +static struct list_head ptr_wins; +static Cursor ptr_idle; +static Cursor ptr_busy; + +void ptr_register(Widget widget) +{ + struct ptr_list *item; + + if (XtWindow(widget)) + XDefineCursor(XtDisplay(widget), XtWindow(widget),ptr_idle); + item = malloc(sizeof(*item)); + memset(item,0,sizeof(*item)); + item->widget = widget; + list_add_tail(&item->list,&ptr_wins); +} + +void ptr_unregister(Widget widget) +{ + struct list_head *item; + struct ptr_list *win,*del; + + del = NULL; + list_for_each(item,&ptr_wins) { + win = list_entry(item, struct ptr_list, list); + if (win->widget == widget) + del = win; + } + if (del) + list_del(&del->list); +} + +void ptr_set(Cursor ptr) +{ + struct list_head *item; + struct ptr_list *win; + Display *dpy = NULL; + + list_for_each(item,&ptr_wins) { + win = list_entry(item, struct ptr_list, list); + if (!XtWindow(win->widget)) + continue; + dpy = XtDisplay(win->widget); + XDefineCursor(dpy, XtWindow(win->widget),ptr); + } + if (dpy) + XSync(dpy,False); +} + +void ptr_init(Widget shell) +{ + XColor white,red,dummy; + Colormap cmap = DefaultColormapOfScreen(XtScreen(shell)); + + INIT_LIST_HEAD(&ptr_wins); + ptr_idle = XCreateFontCursor(XtDisplay(shell),XC_left_ptr); + ptr_busy = XCreateFontCursor(XtDisplay(shell),XC_watch); + if (XAllocNamedColor(XtDisplay(shell),cmap,"white",&white,&dummy) && + XAllocNamedColor(XtDisplay(shell),cmap,"red",&red,&dummy)) { + XRecolorCursor(XtDisplay(shell),ptr_idle,&red,&white); + XRecolorCursor(XtDisplay(shell),ptr_busy,&red,&white); + } +} + +/* --------------------------------------------------------------------- */ +/* handle changer elements */ + +struct changer_elem { + struct list_head list; + int type; + int unit; + Widget draw,label; + Widget menu,mref,mtag,msrc; + struct changer_get_element info; +}; + +static int fd; +static struct changer_params params; +static struct list_head elems; + +static struct changer_elem* +elem_by_typeunit(int type, int unit) +{ + struct list_head *item; + struct changer_elem *elem; + + list_for_each(item,&elems) { + elem = list_entry(item, struct changer_elem, list); + if (elem->type == type && + elem->unit == unit) + return elem; + } + return NULL; +} + +static void update_elem(struct changer_elem *elem) +{ + XmString item,str = NULL; + char buf[64]; + int err; + + /* query */ + elem->info.cge_type = elem->type; + elem->info.cge_unit = elem->unit; + err = ioctl(fd,CHIOGELEM,&elem->info); + if (-1 == err) { + fprintf(stderr,"CHIOGELEM(%d/%d): %s\n", + elem->type,elem->unit,strerror(errno)); + } + + /* build XmString */ + sprintf(buf,"#%03d",elem->unit); + item = XmStringGenerate(buf,NULL,XmMULTIBYTE_TEXT,NULL); + str = XmStringConcatAndFree(str,item); + if (elem->info.cge_flags & CGE_PVOLTAG) { + sprintf(buf," [%-32.32s]",elem->info.cge_pvoltag); + item = XmStringGenerate(buf,NULL,XmMULTIBYTE_TEXT,"voltag"); + str = XmStringConcatAndFree(str,item); + } + if (elem->info.cge_status & CESTATUS_FULL) { + item = XmStringGenerate(" full",NULL,XmMULTIBYTE_TEXT,"full"); + str = XmStringConcatAndFree(str,item); + } + if (elem->info.cge_status & CESTATUS_EXCEPT) { + item = XmStringGenerate(" error",NULL,XmMULTIBYTE_TEXT,"error"); + str = XmStringConcatAndFree(str,item); + } + + /* commit */ + XtVaSetValues(elem->label,XmNlabelString,str,NULL); + XmStringFree(str); +} + +static void +elem_tag_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmSelectionBoxCallbackStruct *cd = call_data; + struct changer_elem *elem = clientdata; + Widget text; + char *str; + + if (args.debug) + fprintf(stderr,"elem_tag_done_cb\n"); + if (cd->reason == XmCR_OK) { + struct changer_set_voltag tag; + text = XmSelectionBoxGetChild(widget,XmDIALOG_TEXT); + str = XmTextGetString(text); + memset(&tag,0,sizeof(tag)); + tag.csv_type = elem->type; + tag.csv_unit = elem->unit; + tag.csv_flags = CSV_PVOLTAG; + if (strlen(str) > 0) + strncpy(tag.csv_voltag,str,32); + else + tag.csv_flags |= CSV_CLEARTAG; + ptr_set(ptr_busy); + if (-1 == ioctl(fd,CHIOSVOLTAG,&tag)) + perror("CHIOSVOLTAG"); + update_elem(elem); + ptr_set(ptr_idle); + } + XtDestroyWidget(XtParent(widget)); +} + +static void +elem_tag_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + Widget box,text; + + if (args.debug) + fprintf(stderr,"elem_tag_cb\n"); + box = XmCreatePromptDialog(elem->draw,"tag",NULL,0); + text = XmSelectionBoxGetChild(box,XmDIALOG_TEXT); + if (elem->info.cge_flags & CGE_PVOLTAG) + XmTextSetString(text,elem->info.cge_pvoltag); + + XtAddCallback(box,XmNokCallback,elem_tag_done_cb,elem); + XtAddCallback(box,XmNcancelCallback,elem_tag_done_cb,elem); + XmdRegisterEditres(XtParent(box)); + XtManageChild(box); +} + +static void +elem_src_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + struct changer_elem *src; + struct changer_move move; + + if (args.debug) + fprintf(stderr,"elem_src_cb\n"); + src = elem_by_typeunit(elem->info.cge_srctype,elem->info.cge_srcunit); + memset(&move,0,sizeof(move)); + move.cm_fromtype = elem->type; + move.cm_fromunit = elem->unit; + move.cm_totype = elem->info.cge_srctype; + move.cm_tounit = elem->info.cge_srcunit; + ptr_set(ptr_busy); + if (-1 == ioctl(fd,CHIOMOVE,&move)) + perror("CHIOMOVE"); + update_elem(elem); + update_elem(src); + ptr_set(ptr_idle); +} + +static void +elem_refresh_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct changer_elem *elem = clientdata; + + ptr_set(ptr_busy); + update_elem(elem); + ptr_set(ptr_idle); +} + +static void +elem_menu_eh(Widget widget, XtPointer clientdata, XEvent *event, Boolean *cont) +{ + struct changer_elem *elem = clientdata; + Boolean tag,src; + + if (args.debug) + fprintf(stderr,"elem_menu_eh\n"); + tag = (elem->info.cge_status & CESTATUS_FULL) ? True : False; + XtVaSetValues(elem->mtag,XmNsensitive,tag,NULL); + if (elem->msrc) { + src = (elem->info.cge_status & CESTATUS_FULL) && + (elem->info.cge_flags & CGE_SRC); + XtVaSetValues(elem->msrc,XmNsensitive,src,NULL); + } + XmMenuPosition(elem->menu,(XButtonPressedEvent*)event); + XtManageChild(elem->menu); +} + +static struct changer_elem* +add_elem(Widget parent, int type, int unit) +{ + struct changer_elem *elem; + Cardinal n = 0; + Arg args[4]; + char name[32]; + + elem = malloc(sizeof(*elem)); + memset(elem,0,sizeof(*elem)); + sprintf(name,"%d/%d",type,unit); + elem->type = type; + elem->unit = unit; + elem->draw = XtVaCreateManagedWidget("d",xmDrawingAreaWidgetClass,parent, + NULL); + elem->label = XtVaCreateManagedWidget(name,xmLabelWidgetClass,elem->draw, + NULL); + update_elem(elem); + + /* dnd */ + XtAddCallback(elem->label,XmNconvertCallback,elem_convert,elem); + XtAddCallback(elem->draw,XmNdestinationCallback,elem_destination,elem); + XtSetArg(args[n], XmNimportTargets, targets); n++; + XtSetArg(args[n], XmNnumImportTargets, ntargets); n++; + XmeDropSink(elem->draw,args,n); + + /* context menu */ + elem->menu = XmCreatePopupMenu(elem->draw,"menu",NULL,0); + XtAddEventHandler(elem->draw,ButtonPressMask,False,elem_menu_eh,elem); + elem->mref = XtVaCreateManagedWidget("ref",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->mref,XmNactivateCallback,elem_refresh_cb,elem); + elem->mtag = XtVaCreateManagedWidget("tag",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->mtag,XmNactivateCallback,elem_tag_cb,elem); + if (elem->type == CHET_DT) { + elem->msrc = XtVaCreateManagedWidget("src",xmPushButtonWidgetClass, + elem->menu,NULL); + XtAddCallback(elem->msrc,XmNactivateCallback,elem_src_cb,elem); + } + + list_add_tail(&elem->list,&elems); + return elem; +} + +/* --------------------------------------------------------------------- */ +/* drag'n'drop stuff */ + +static void +elem_convert(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmConvertCallbackStruct *ccs = call_data; + struct changer_elem *elem = clientdata; + unsigned long *largs; + char *name; + Atom *targs; + int n; + + if (args.debug) { + name = XGetAtomName(XtDisplay(widget),ccs->target); + fprintf(stderr,"elem_convert %s\n",name); + XFree(name); + } + + if ((ccs->target == XA_TARGETS) || + (ccs->target == _MOTIF_EXPORT_TARGETS)) { + targs = (Atom*)XtMalloc(sizeof(Atom)*8); + n = 0; + targs[n++] = _XMOVER_TYPEUNIT; + targs[n++] = _XMOVER_REFRESH; + ccs->value = targs; + ccs->length = n; + ccs->type = XA_ATOM; + ccs->format = 32; + ccs->status = XmCONVERT_MERGE; + } + if (ccs->target == _XMOVER_TYPEUNIT) { + largs = (unsigned long*)XtMalloc(sizeof(*largs)); + largs[0] = (elem->type << 16) | elem->unit; + ccs->value = largs; + ccs->length = 1; + ccs->type = XA_INTEGER; + ccs->format = 32; + ccs->status = XmCONVERT_DONE; + } + if (ccs->target == _XMOVER_REFRESH) { + ptr_set(ptr_busy); + update_elem(elem); + ptr_set(ptr_idle); + ccs->value = NULL; + ccs->length = 0; + ccs->type = XA_INTEGER; + ccs->format = 32; + ccs->status = XmCONVERT_DONE; + } +} + +static void +elem_transfer(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmSelectionCallbackStruct *scs = call_data; + struct changer_elem *elem = clientdata; + unsigned long *ldata = scs->value; + XmTransferStatus status = XmTRANSFER_DONE_SUCCEED; + Time time = XtLastTimestampProcessed(XtDisplay(widget)); + int pending,i; + char *name; + + if (args.debug) { + name = XGetAtomName(XtDisplay(widget),scs->target); + fprintf(stderr,"elem_transfer %s\n",name); + XFree(name); + } + + pending = scs->remaining; + if (scs->target == XA_TARGETS) { + for (i = 0; i < scs->length; i++) { + if (ldata[i] == _XMOVER_TYPEUNIT) { + XmTransferValue(scs->transfer_id, ldata[i], elem_transfer, + elem, time); + pending++; + } + } + if (pending == scs->remaining) { + /* no target found */ + status = XmTRANSFER_DONE_FAIL; + } + } + if (scs->target == _XMOVER_TYPEUNIT) { + struct changer_move move; + + if (args.debug) + fprintf(stderr,"move: %ld/%ld => %d/%d\n", + ldata[0] >> 16, ldata[0] & 0xffff, + elem->type, elem->unit); + memset(&move,0,sizeof(move)); + move.cm_fromtype = ldata[0] >> 16; + move.cm_fromunit = ldata[0] & 0xffff; + move.cm_totype = elem->type; + move.cm_tounit = elem->unit; + ptr_set(ptr_busy); + if (move.cm_fromtype != move.cm_totype || + move.cm_fromunit != move.cm_tounit) + if (-1 == ioctl(fd,CHIOMOVE,&move)) + perror("CHIOMOVE"); + update_elem(elem); + ptr_set(ptr_idle); + XmTransferValue(scs->transfer_id, _XMOVER_REFRESH, elem_transfer, + elem, time); + pending++; + } + XFree(scs->value); + if (1 == pending) + XmTransferDone(scs->transfer_id, status); +} + +static void +elem_destination(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XmDestinationCallbackStruct *dcs = call_data; + struct changer_elem *elem = clientdata; + Time time = XtLastTimestampProcessed(XtDisplay(widget)); + + if (args.debug) + fprintf(stderr,"elem_destination\n"); + XmTransferValue(dcs->transfer_id, XA_TARGETS, elem_transfer, elem, time); +} + +/* --------------------------------------------------------------------- */ +/* gui + basic callbacks */ + +void +destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + XtDestroyWidget(clientdata); +} + +static void +about_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + Widget msgbox; + + msgbox = XmCreateInformationDialog(widget,"aboutbox",NULL,0); + XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON)); + XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON)); + XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox); + XtManageChild(msgbox); +} + +static void +close_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + Widget shell = clientdata; + XtDestroyWidget(shell); + exit(0); +} + +static void +refresh_cb(Widget widget, XtPointer clientdata, XtPointer call_data) +{ + struct list_head *item; + struct changer_elem *elem; + + ptr_set(ptr_busy); + list_for_each(item,&elems) { + elem = list_entry(item, struct changer_elem, list); + update_elem(elem); + } + ptr_set(ptr_idle); +} + +static void create_widgets(Widget shell) +{ + Widget form,menubar,menu,push,frame,scroll,rowcol; + int i; + + /* shell stuff */ + XmdRegisterEditres(shell); + XmAddWMProtocolCallback(shell,XA_WM_DELETE_WINDOW,close_cb,shell); + + /* form container */ + form = XtVaCreateManagedWidget("form", xmFormWidgetClass, shell, + NULL); + + /* menu- & toolbar */ + menubar = XmCreateMenuBar(form,"bar",NULL,0); + XtManageChild(menubar); + + /* menu -- file */ + menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0); + XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + push = XtVaCreateManagedWidget("quit",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,close_cb,shell); + + /* menu -- changer */ + menu = XmCreatePulldownMenu(menubar,"changerM",NULL,0); + XtVaCreateManagedWidget("changer",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + push = XtVaCreateManagedWidget("refresh",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,refresh_cb,NULL); + + /* menu - help */ + menu = XmCreatePulldownMenu(menubar,"helpM",NULL,0); + push = XtVaCreateManagedWidget("help",xmCascadeButtonWidgetClass,menubar, + XmNsubMenuId,menu,NULL); + XtVaSetValues(menubar,XmNmenuHelpWidget,push,NULL); + push = XtVaCreateManagedWidget("man",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,man_cb,"xmover"); + XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL); + push = XtVaCreateManagedWidget("about",xmPushButtonWidgetClass,menu,NULL); + XtAddCallback(push,XmNactivateCallback,about_cb,NULL); + + /* ie elems */ + frame = XtVaCreateManagedWidget("ie", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + frame,NULL); + for (i = 0; i < params.cp_nportals; i++) + add_elem(rowcol, CHET_IE, i); + + /* dt elems */ + frame = XtVaCreateManagedWidget("dt", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + frame,NULL); + for (i = 0; i < params.cp_ndrives; i++) + add_elem(rowcol, CHET_DT, i); + + /* st elems */ + frame = XtVaCreateManagedWidget("st", xmFrameWidgetClass, form, NULL); + XtVaCreateManagedWidget("label",xmLabelWidgetClass,frame,NULL); + scroll = XmCreateScrolledWindow(frame,"scroll",NULL,0); + XtManageChild(scroll); + rowcol = XtVaCreateManagedWidget("rc",xmRowColumnWidgetClass, + scroll,NULL); + for (i = 0; i < params.cp_nslots; i++) + add_elem(rowcol, CHET_ST, i); +} + +/* --------------------------------------------------------------------- */ +/* main */ + +static void usage(void) +{ + fprintf(stderr, + "xmover -- X11 frontend for scsi media changers\n" + "options:\n" + " -debug enable debug messages\n" + " -device use instead of /dev/sch0\n"); +} + +int +main(int argc, char *argv[]) +{ + Widget app_shell; + int err; + + /* init X11 */ + XtSetLanguageProc(NULL,NULL,NULL); + app_shell = XtVaAppInitialize(&app_context, "xmover", + opt_desc, opt_count, + &argc, argv, + fallback_ressources, + NULL); + XtGetApplicationResources(app_shell,&args, + args_desc,args_count, + NULL,0); + if (args.help) { + usage(); + exit(1); + } + + /* init changer */ + fd = open(args.device,O_RDONLY); + if (-1 == fd) { + fprintf(stderr,"open %s: %s\n",args.device,strerror(errno)); + exit(1); + } + err = ioctl(fd,CHIOGPARAMS,¶ms); + if (-1 == err) { + perror("CHIOGPARAMS"); + exit(1); + } + INIT_LIST_HEAD(&elems); + + init_atoms(XtDisplay(app_shell)); + if (!args.debug) + stderr_init(app_shell); + ptr_init(app_shell); + ptr_register(app_shell); + create_widgets(app_shell); + + XtRealizeWidget(app_shell); + XtAppMainLoop(app_context); + return 0; +} diff --git a/xmover.h b/xmover.h new file mode 100644 index 0000000..7e4f2b2 --- /dev/null +++ b/xmover.h @@ -0,0 +1,125 @@ +"xmover.geometry: 400x500", +"xmover.deleteResponse: DO_NOTHING", +"xmover*XmDialogShell.deleteResponse: DESTROY", +"", +"*renderTable: error,full,voltag", +"*renderTable.fontType: FONT_IS_FONTSET", +"*renderTable.fontName: " +" -*-helvetica-medium-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.error.renditionForeground: darkred", +"*renderTable.error.fontType: FONT_IS_FONTSET", +"*renderTable.error.fontName: " +" -*-helvetica-bold-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.full.renditionForeground: darkgreen", +"*renderTable.full.fontType: FONT_IS_FONTSET", +"*renderTable.full.fontName: " +" -*-helvetica-bold-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"*renderTable.voltag.fontType: FONT_IS_FONTSET", +"*renderTable.voltag.fontName: " +" -*-fixed-medium-r-normal--*-120-*-*-*-*, " +" -*-*-*-r-normal--*-120-*-*-*-*", +"", +"xmover.form.?.leftAttachment: ATTACH_FORM", +"xmover.form.?.rightAttachment: ATTACH_FORM", +"xmover.form.ie.topAttachment: ATTACH_WIDGET", +"xmover.form.ie.topWidget: bar", +"xmover.form.dt.topAttachment: ATTACH_WIDGET", +"xmover.form.dt.topWidget: ie", +"xmover.form.st.topAttachment: ATTACH_WIDGET", +"xmover.form.st.topWidget: dt", +"xmover.form.st.bottomAttachment: ATTACH_FORM", +"xmover.form.tool.leftOffset: 5", +"xmover.form.tool.rightOffset: 5", +"xmover.form.XmFrame.leftOffset: 5", +"xmover.form.XmFrame.rightOffset: 5", +"xmover.form.XmFrame.topOffset: 5", +"xmover.form.XmFrame.bottomOffset: 5", +"", +"xmover.form.XmFrame.marginWidth: 5", +"xmover.form.XmFrame.marginHeight: 5", +"xmover.form.XmFrame.label.frameChildType: FRAME_TITLE_CHILD", +"", +"xmover.form.*.rc.packing: PACK_COLUMN", +"xmover.form.*.rc.orientation: VERTICAL", +"xmover.form.*.rc.resizeHeight: True", +"xmover.form.*.rc.numColumns: 1", +"xmover.form.*.scroll.visualPolicy: CONSTANT", +"xmover.form.*.scroll.scrollingPolicy: AUTOMATIC", +"xmover.form.*.scroll.scrollBarDisplayPolicy: AS_NEEDED", +"xmover.form.*.scroll.scrollBarPlacement: BOTTOM_RIGHT", +"", +"xmover.form.*.rc.d.background: gray95", +"xmover.form.*.rc.d.marginWidth: 0", +"xmover.form.*.rc.d.marginHeight: 0", +"xmover.form.*.rc.d.XmLabel.background: gray95", +"xmover.form.*.rc.d.XmLabel.borderWidth: 0", +"xmover.form.*.rc.d.XmLabel.marginLeft: 5", +"xmover.form.*.rc.d.XmLabel.marginRight: 5", +"", +"xmover.title: xmover", +"xmover*ie.label.labelString: Import/export elements (mailslot)", +"xmover*dt.label.labelString: Data transfer elements (drives)", +"xmover*st.label.labelString: Storage elements (slots)", +"xmover*menu.ref.labelString: Reload status", +"xmover*menu.src.labelString: Move back volume", +"xmover*menu.tag.labelString: Edit volume tag", +"", +"xmover*bar.file.labelString: File", +"xmover*bar.file.mnemonic: F", +"xmover*bar.changer.labelString: Media changer", +"xmover*bar.changer.mnemonic: M", +"xmover*bar.help.labelString: Help", +"xmover*bar.help.mnemonic: H", +"", +"xmover*bar*quit.labelString: Quit", +"xmover*bar*quit.mnemonic: Q", +"xmover*bar*quit.acceleratorText: Q", +"xmover*bar*quit.accelerator: Q", +"", +"xmover*bar*refresh.labelString: Reload status for all elements", +"xmover*bar*refresh.mnemonic: R", +"xmover*bar*refresh.acceleratorText: Ctrl+L", +"xmover*bar*refresh.accelerator: CtrlL", +"", +"xmover*bar*man.labelString: Manual page ...", +"xmover*bar*man.mnemonic: M", +"xmover*bar*man.acceleratorText: F1", +"xmover*bar*man.accelerator: F1", +"xmover*bar*about.labelString: About ...", +"", +"xmover.errbox_popup.title: Errors", +"xmover*tag_popup.title: Volume tag", +"xmover*tag_popup*selectionLabelString: New volume tag?", +"xmover*aboutbox_popup.title: About xmover", +"xmover*aboutbox_popup*messageString: " +" xmover - X11 frontend for scsi media changers\\n" +" \\n" +" (c) 2002 Gerd Knorr ", +"", +"xmover*man_popup.title: Manual page", +"xmover*man_popup*okLabelString: close window", +"xmover*man_popup*label.labelString: please wait ...", +"", +"xmover*man_popup.deleteResponse: DESTROY", +"xmover*man_popup*view.width: 500", +"xmover*man_popup*view.height: 600", +"xmover*man_popup*view.scrollingPolicy: AUTOMATIC", +"xmover*man_popup*view.scrollBarPlacement: BOTTOM_RIGHT", +"", +"xmover*man_popup*label.alignment: ALIGNMENT_BEGINNING", +"xmover*man_popup*label.marginWidth: 5", +"xmover*man_popup*label.marginHeight: 5", +"xmover*man_popup*label.renderTable: bold,underline", +"xmover*man_popup*label.renderTable.fontType: FONT_IS_FONTSET", +"xmover*man_popup*label.renderTable.fontName: " +" -*-fixed-medium-r-normal--13-*-*-*-*-*,", +" -*-*-*-r-normal--*-120-*-*-*-*", +"", +"xmover*man_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET", +"xmover*man_popup*label.renderTable.bold.fontName: " +" -*-fixed-bold-r-normal--13-*-*-*-*-*,", +" -*-*-*-r-normal--*-120-*-*-*-*", +"xmover*man_popup*label.renderTable.underline.underlineType: SINGLE_LINE", diff --git a/xmover.man b/xmover.man new file mode 100644 index 0000000..5d42490 --- /dev/null +++ b/xmover.man @@ -0,0 +1,53 @@ +.TH xmover 1 "(c) 2002 Gerd Knorr" +.SH NAME +xmover - X11 frontend for scsi media changers +.SH SYNOPSIS +.B xmover [ options ] +.SH DESCRIPTION +.B xmover +is a X11 utility which can control media changers using the scsi media +changer device driver. +.SH OPTIONS +.TP +.B -debug +enable debug output. +.TP +.B -device +Use instead of /dev/sch0. +.P +The usual toolkit options (-geometry and friends) are supported too. +.SH USING THE TOOL +Just start it. +.B xmover +will read the changer configuration at startup and present a window +with all changer elements when done. It displays the volume tags (if +present) and the element status (full/empty, ...). Every changer +element has a context menu with some functions, you can edit the +volume labels for example. +.P +Moving media from one element to another simply works with drag and +drop. +.B xmover +uses Motif, thus the middle mouse button will start drags. +.SH BUGS +There is no support for double-sided media. +.SH SEE ALSO +mover(1) +.SH AUTHOR +Gerd Knorr +.SH COPYRIGHT +Copyright (C) 2002 Gerd Knorr +.P +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +.P +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.P +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -- cgit