]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
eject: initial merge eject and util-linux
authorMichal Luscon <mluscon@redhat.com>
Thu, 8 Dec 2011 15:10:18 +0000 (16:10 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 27 Mar 2012 11:04:58 +0000 (13:04 +0200)
Signed-off-by: Michal Luscon <mluscon@redhat.com>
configure.ac
misc-utils/Makefile.am
misc-utils/eject.1 [new file with mode: 0644]
misc-utils/eject.c [new file with mode: 0644]
misc-utils/i18n.h [new file with mode: 0644]

index 2140bc596a75ea905880cbe4f5f1f9dd1c057e0c..4e9b5f4ee99541dd9fd7600a72abbf0d8f252d94 100644 (file)
@@ -862,6 +862,14 @@ ruman1dir='${mandir}/ru/man1'
 AC_SUBST([ruman1dir])
 
 
+AC_ARG_ENABLE([eject],
+  AS_HELP_STRING([--disable-eject], [do not build eject]),
+  [], enable_eject=yes
+)
+UL_BUILD_INIT([eject])
+UL_REQUIRES_LINUX([eject])
+
+
 AC_ARG_ENABLE([agetty],
   AS_HELP_STRING([--disable-agetty], [do not build agetty]),
   [], enable_agetty=yes
@@ -870,6 +878,8 @@ UL_BUILD_INIT([agetty])
 AM_CONDITIONAL(BUILD_AGETTY, test "x$build_agetty" = xyes)
 
 
+
+
 AC_ARG_ENABLE([cramfs],
   AS_HELP_STRING([--disable-cramfs], [do not build fsck.cramfs, mkfs.cramfs]),
   [], enable_cramfs=check
index acf2c9d1ae21640bfec56f889b1f122da565a6bf..097e1f969cd98d8d9c397fac8d251fa4a90a245a 100644 (file)
@@ -32,6 +32,12 @@ dist_man_MANS = \
 
 namei_SOURCES = namei.c $(top_srcdir)/lib/strutils.c
 
+
+usrbin_exec_PROGRAMS += eject
+eject_SOURCES = eject.c
+dist_man_MANS += eject.1
+
+
 if BUILD_DDATE
 usrbin_exec_PROGRAMS += ddate
 dist_man_MANS += ddate.1
diff --git a/misc-utils/eject.1 b/misc-utils/eject.1
new file mode 100644 (file)
index 0000000..ae92b7f
--- /dev/null
@@ -0,0 +1,316 @@
+.\" This file Copyright (C) 1994-2005 Jeff Tranter
+.\" (tranter@pobox.com)
+.\" It may be distributed under the GNU Public License, version 2, or
+.\" any higher version. See section COPYING of the GNU Public license
+.\" for conditions under which this file may be redistributed.
+.TH EJECT 1 "18 May 2001" "Linux" "User Commands"
+.SH NAME
+eject \- eject removable media
+.SH SYNOPSIS
+eject \-h
+.br
+eject [\-vnrsfqp] [<name>]
+.br
+eject [\-vn] \-d
+.br
+eject [\-vn] \-a on|off|1|0 [<name>]
+.br
+eject [\-vn] \-c slot [<name>]
+.br
+eject [\-vn] \-t [<name>]
+.br
+eject [-vn] -T [<name>]
+.br
+eject [\-vn] \-x <speed> [<name>]
+.br
+eject \-V
+
+.SH DESCRIPTION
+
+.B Eject
+allows removable media (typically a CD-ROM, floppy disk, tape, or JAZ
+or ZIP disk) to be ejected under software control. The command can
+also control some multi-disc CD-ROM changers, the auto-eject feature
+supported by some devices, and close the disc tray of some CD-ROM
+drives.
+
+The device corresponding to <name> is ejected. The name can be a
+device file or mount point, either a full path or with the leading
+"/dev", "/media" or "/mnt" omitted. If no name is specified, the default name
+"cdrom" is used.
+
+There are four different methods of ejecting, depending on whether the
+device is a CD-ROM, SCSI device, removable floppy, or tape. By default
+eject tries all four methods in order until it succeeds.
+
+If the device is currently mounted, it is unmounted before ejecting.
+
+.PP
+.SH "COMMAND\-LINE OPTIONS"
+.TP 0.5i
+.B \-h
+This option causes
+.B eject
+to display a brief description of the command options.
+
+.TP 0.5i
+.B \-v
+This makes
+.B eject
+run in verbose mode; more information is displayed about what the
+command is doing.
+
+.TP 0.5i
+.B \-d
+If invoked with this option,
+.B eject
+lists the default device name.
+
+.TP 0.5i
+.B \-a on|1|off|0
+This option controls the auto-eject mode, supported by some devices.
+When enabled, the drive automatically ejects when the device is
+closed.
+
+.TP 0.5i
+.B \-c <slot>
+With this option a CD slot can be selected from an ATAPI/IDE CD-ROM
+changer. Linux 2.0 or higher is required to use this feature. The
+CD-ROM drive can not be in use (mounted data CD or playing a music CD)
+for a change request to work. Please also note that the first slot of
+the changer is referred to as 0, not 1.
+
+.TP 0.5i
+.B \-t
+With this option the drive is given a CD-ROM tray close command. Not
+all devices support this command.
+
+.TP 0.5i
+.B \-T
+With this option the drive is given a CD-ROM tray close command if
+it's opened, and a CD-ROM tray eject command if it's closed. Not all
+devices support this command, because it uses the above CD-ROM tray
+close command.
+
+.TP 0.5i
+.B \-x <speed>
+With this option the drive is given a CD-ROM select speed command.
+The speed argument is a number indicating the desired speed (e.g. 8
+for 8X speed), or 0 for maximum data rate. Not all devices support
+this command and you can only specify speeds that the drive is capable
+of. Every time the media is changed this option is cleared. This
+option can be used alone, or with the \-t and \-c options.
+
+.TP 0.5i
+.B \-n
+With this option the selected device is displayed but no action is
+performed.
+
+.TP 0.5i
+.B \-r
+This option specifies that the drive should be ejected using a
+CDROM eject command.
+.TP 0.5i
+
+.B \-s
+This option specifies that the drive should be ejected using
+SCSI commands.
+
+.TP 0.5i
+.B \-f
+This option specifies that the drive should be ejected using a
+removable floppy disk eject command.
+
+.TP 0.5i
+.B \-q
+This option specifies that the drive should be ejected using a
+tape drive offline command.
+
+.TP 0.5i
+.B \-p
+This option allow you to use /proc/mounts instead /etc/mtab. It
+also passes the \-n option to umount(1).
+
+.TP 0.5i
+.B \-m
+This option allows eject to work with device drivers which automatically
+mount removable media and therefore must be always mount()ed.
+The option tells eject to not try to unmount the given device,
+even if it is mounted according to /etc/mtab or /proc/mounts.
+
+.TP 0.5i
+.B \-V
+This option causes
+.B eject
+to display the program version and exit.
+
+.SH LONG OPTIONS
+All options have corresponding long names, as listed below. The long
+names can be abbreviated as long as they are unique.
+
+.br
+\-h \-\-help
+.br
+\-v \-\-verbose
+.br
+\-d \-\-default
+.br
+\-a \-\-auto
+.br
+\-c \-\-changerslot
+.br
+\-t \-\-trayclose
+.br
+\-t \-\-traytoggle
+.br
+\-x \-\-cdspeed
+.br
+\-n \-\-noop
+.br
+\-r \-\-cdrom
+.br
+\-s \-\-scsi
+.br
+\-f \-\-floppy
+.br
+\-q \-\-tape
+.br
+\-V \-\-version
+.br
+\-p \-\-proc
+.br
+\-m \-\-no-unmount
+.br
+
+.SH EXAMPLES
+.PP
+Eject the default device:
+.IP
+eject
+.PP
+Eject a device or mount point named cdrom:
+.IP
+eject cdrom
+.PP
+Eject using device name:
+.IP
+eject /dev/cdrom
+.PP
+Eject using mount point:
+.IP
+eject /mnt/cdrom/
+.PP
+Eject 4th IDE device:
+.IP
+eject hdd
+.PP
+Eject first SCSI device:
+.IP
+eject sda
+.PP
+Eject using SCSI partition name (e.g. a ZIP drive):
+.IP
+eject sda4
+.PP
+Select 5th disc on multi-disc changer:
+.IP
+eject \-v \-c4 /dev/cdrom
+.PP
+Turn on auto-eject on a SoundBlaster CD-ROM drive:
+.IP
+eject \-a on /dev/sbpcd
+
+.SH EXIT STATUS
+.PP
+
+Returns 0 if operation was successful, 1 if operation failed or command
+syntax was not valid.
+
+.SH NOTES
+.PP
+
+.B Eject
+only works with devices that support one or more of the four methods
+of ejecting. This includes most CD-ROM drives (IDE, SCSI, and
+proprietary), some SCSI tape drives, JAZ drives, ZIP drives (parallel
+port, SCSI, and IDE versions), and LS120 removable floppies. Users
+have also reported success with floppy drives on Sun SPARC and Apple
+Macintosh systems. If
+.B eject
+does not work, it is most likely a limitation of the kernel driver
+for the device and not the
+.B eject
+program itself.
+
+The \-r, \-s, \-f, and \-q options allow controlling which methods are
+used to eject. More than one method can be specified. If none of these
+options are specified, it tries all four (this works fine in most
+cases).
+
+.B Eject
+may not always be able to determine if the device is mounted (e.g. if
+it has several names). If the device name is a symbolic link,
+.B eject
+will follow the link and use the device that it points to.
+
+If
+.B eject
+determines that the device can have multiple partitions, it will
+attempt to unmount all mounted partitions of the device before
+ejecting. If an unmount fails, the program will not attempt to eject
+the media.
+
+You can eject an audio CD. Some CD-ROM drives will refuse to open the
+tray if the drive is empty. Some devices do not support the tray close
+command.
+
+If the auto-eject feature is enabled, then the drive will always be
+ejected after running this command. Not all Linux kernel CD-ROM
+drivers support the auto-eject mode. There is no way to find out the
+state of the auto-eject mode.
+
+You need appropriate privileges to access the device files. Running as
+root or setuid root is required to eject some devices (e.g. SCSI
+devices).
+
+The heuristic used to find a device, given a name, is as follows. If
+the name ends in a trailing slash, it is removed (this is to support
+filenames generated using shell file name completion). If the name
+starts with '.' or '/', it tries to open it as a device file or mount
+point. If that fails, it tries prepending '/dev/', '/media/' ,'/mnt/',
+\&'/dev/cdroms', '/dev/rdsk/', '/dev/dsk/', and finally './' to the name,
+until a
+device file or mount point is found that can be opened. The program
+checks /etc/mtab for mounted devices. If that fails, it also checks
+/etc/fstab for mount points of currently unmounted devices.
+
+Creating symbolic links such as /dev/cdrom or /dev/zip is recommended
+so that
+.B eject
+can determine the appropriate devices using easily remembered names.
+
+To save typing you can create a shell alias for the eject options that
+work for your particular setup.
+
+.SH AUTHOR
+.B Eject
+was written by Jeff Tranter (tranter@pobox.com) and is released
+under the conditions of the GNU General Public License. See the file
+COPYING and notes in the source code for details.
+
+The \-x option was added by Nobuyuki Tsuchimura (tutimura@nn.iij4u.or.jp),
+with thanks to Roland Krivanek (krivanek@fmph.uniba.sk) and his
+cdrom_speed command.
+
+The -T option was added by Sybren Stuvel (sybren@thirdtower.com), with
+big thanks to Benjamin Schwenk (benjaminschwenk@yahoo.de).
+
+ .SH SEE ALSO
+
+ mount(2), umount(2), mount(8), umount(8)
+
+.SH SEE ALSO
+
+mount(2), umount(2), mount(8), umount(8)
+.br
+/usr/src/linux/Documentation/cdrom/
diff --git a/misc-utils/eject.c b/misc-utils/eject.c
new file mode 100644 (file)
index 0000000..172278e
--- /dev/null
@@ -0,0 +1,1138 @@
+/********************************************************************
+ *
+ *             L I N U X   E J E C T   C O M M A N D
+ *
+ *               by Jeff Tranter (tranter@pobox.com)
+ *
+ ********************************************************************
+ *
+ * Copyright (C) 1994-2005 Jeff Tranter (tranter@pobox.com)
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ********************************************************************
+ *
+ * See the man page for a description of what this program does and what
+ * the requirements to run it are.
+ *
+ */
+
+#include "i18n.h"
+
+
+#define DEFAULTDEVICE "/dev/sr0"
+
+
+#include <linux/version.h>
+/* handy macro found in 2.1 kernels, but not in older ones */
+#ifndef KERNEL_VERSION
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#endif
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#ifdef GETOPTLONG
+#include <getopt.h>
+#endif /* GETOPTLONG */
+#include <errno.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/mtio.h>
+#include <linux/types.h>
+#include <linux/cdrom.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
+#include <linux/ucdrom.h>
+#endif
+#include <linux/fd.h>
+#include <sys/mount.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_ioctl.h>
+#include <sys/time.h>
+
+/* Used by the ToggleTray() function. If ejecting the tray takes this
+ * time or less, the tray was probably already ejected, so we close it
+ * again.
+ */
+#define TRAY_WAS_ALREADY_OPEN_USECS  200000    /* about 0.2 seconds */
+
+
+#define CLOSE(fd) if (close(fd)==-1) { \
+    perror(programName); \
+    exit(1); \
+}
+
+#define FCLOSE(fd) if (fclose(fd)==-1) { \
+    perror(programName); \
+    exit(1); \
+}
+
+/* Global Variables */
+const char *version = VERSION; /* program version */
+int a_option = 0; /* command flags and arguments */
+int c_option = 0;
+int d_option = 0;
+int f_option = 0;
+int h_option = 0;
+int n_option = 0;
+int q_option = 0;
+int r_option = 0;
+int s_option = 0;
+int t_option = 0;
+int T_option = 0;
+int v_option = 0;
+int x_option = 0;
+int p_option = 0;
+int m_option = 0;
+int a_arg = 0;
+int c_arg = 0;
+int x_arg = 0;
+char *programName; /* used in error messages */
+
+/*
+ * These are the basenames of devices which can have multiple
+ * partitions per device.
+ */
+const char *partitionDevice[] = {
+       "hd",
+       "sd",
+       "xd",
+       "dos_hd",
+       "mfm",
+       "ad",
+       "ed",
+       "ftl",
+       "pd",
+       0};
+
+
+/* Display command usage on standard error and exit. */
+static void usage()
+{
+//    perror(_("%s: device is `%s'\n"));
+       fprintf(stderr,_(
+"Eject version %s by Jeff Tranter (tranter@pobox.com)\n"
+"Usage:\n"
+"  eject -h                            -- display command usage and exit\n"
+"  eject -V                            -- display program version and exit\n"
+"  eject [-vnrsfq] [<name>]            -- eject device\n"
+"  eject [-vn] -d                      -- display default device\n"
+"  eject [-vn] -a on|off|1|0 [<name>]  -- turn auto-eject feature on or off\n"
+"  eject [-vn] -c <slot> [<name>]      -- switch discs on a CD-ROM changer\n"
+"  eject [-vn] -t [<name>]             -- close tray\n"
+"  eject [-vn] -T [<name>]             -- toggle tray\n"
+"  eject [-vn] -x <speed> [<name>]     -- set CD-ROM max speed\n"
+"Options:\n"
+"  -v\t-- enable verbose output\n"
+"  -n\t-- don't eject, just show device found\n"
+"  -r\t-- eject CD-ROM\n"
+"  -s\t-- eject SCSI device\n"
+"  -f\t-- eject floppy\n"
+"  -q\t-- eject tape\n"
+"  -p\t-- use /proc/mounts instead of /etc/mtab\n"
+"  -m\t-- do not unmount device even if it is mounted\n"
+)
+, version);
+#ifdef GETOPTLONG
+       fprintf(stderr,_(
+"Long options:\n"
+"  -h --help   -v --verbose     -d --default\n"
+"  -a --auto   -c --changerslot  -t --trayclose  -x --cdspeed\n"
+"  -r --cdrom  -s --scsi        -f --floppy\n"
+"  -q --tape   -n --noop        -V --version\n"
+"  -p --proc   -m --no-unmount -T --traytoggle\n"));
+#endif /* GETOPTLONG */
+       fprintf(stderr,_(
+"Parameter <name> can be a device file or a mount point.\n"
+"If omitted, name defaults to `%s'.\n"
+"By default tries -r, -s, -f, and -q in order until success.\n"),
+                       DEFAULTDEVICE);
+  exit(1);
+}
+
+
+/* Handle command line options. */
+static void parse_args(int argc, char **argv, char **device)
+{
+       const char *flags = "a:c:x:dfhnqrstTvVpm";
+#ifdef GETOPTLONG
+       static struct option long_options[] =
+       {
+               {"help",        no_argument,       NULL, 'h'},
+               {"verbose",     no_argument,       NULL, 'v'},
+               {"default",     no_argument,       NULL, 'd'},
+               {"auto",        required_argument, NULL, 'a'},
+               {"changerslot", required_argument, NULL, 'c'},
+               {"trayclose",   no_argument,       NULL, 't'},
+               {"traytoggle",  no_argument,       NULL, 'T'},
+               {"cdspeed",     required_argument, NULL, 'x'},
+               {"noop",        no_argument,       NULL, 'n'},
+               {"cdrom",       no_argument,       NULL, 'r'},
+               {"scsi",        no_argument,       NULL, 's'},
+               {"floppy",      no_argument,       NULL, 'f'},
+               {"tape",        no_argument,       NULL, 'q'},
+               {"version",     no_argument,       NULL, 'V'},
+               {"proc",        no_argument,       NULL, 'p'},
+               {"no-unmount",  no_argument,       NULL, 'm'},
+               {0, 0, 0, 0}
+       };
+       int option_index;
+#endif /* GETOPTLONG */
+       int c;
+
+#ifdef GETOPTLONG
+       while ((c = getopt_long(argc, argv, flags, long_options, &option_index)) != EOF) {
+#else
+       while ((c = getopt(argc, argv, flags)) != EOF) {
+#endif /* GETOPTLONG */
+               switch (c) {
+                 case 'a':
+                         a_option = 1;
+                         if (!strcmp(optarg, "0"))
+                                 a_arg = 0;
+                         else if (!strcmp(optarg, "off"))
+                                 a_arg = 0;
+                         else if (!strcmp(optarg, "1"))
+                                 a_arg = 1;
+                         else if (!strcmp(optarg, "on"))
+                                 a_arg = 1;
+                         else {
+                                 fprintf(stderr, _("%s: invalid argument to --auto/-a option\n"), programName);
+                                 exit(1);
+                         }
+                         break;
+                 case 'c':
+                         c_option = 1;
+                         /* atoi() returns 0 on error, so "0" must be parsed separately */
+                         if (!strcmp(optarg, "0"))
+                                 c_arg = 0;
+                         else {
+                                 c_arg = atoi(optarg);
+                                 if (c_arg <= 0) {
+                                         fprintf(stderr, _("%s: invalid argument to --changerslot/-c option\n"), programName);
+                                         exit(1);
+                                 }
+                         }
+                         break;
+                 case 'x':
+                         x_option = 1;
+                         if (!strcmp(optarg, "0"))
+                                 x_arg = 0;
+                         else {
+                                 x_arg = atoi(optarg);
+                                 if (x_arg <= 0) {
+                                         fprintf(stderr, _("%s: invalid argument to --cdspeed/-x option\n"), programName);
+                                         exit(1);
+                                 }
+                         }
+                         break;
+                 case 'd':
+                         d_option = 1;
+                         break;
+                 case 'f':
+                         f_option = 1;
+                         break;
+                 case 'h':
+                         usage();
+                         exit(0);
+                         break;
+                 case 'm':
+                         m_option = 1;
+                         break;
+                 case 'n':
+                         n_option = 1;
+                         break;
+                 case 'p':
+                         p_option = 1;
+                         break;
+                 case 'q':
+                         q_option = 1;
+                         break;
+                 case 'r':
+                         r_option = 1;
+                         break;
+                 case 's':
+                         s_option = 1;
+                         break;
+                 case 't':
+                         t_option = 1;
+                         break;
+                 case 'T':
+                         T_option = 1;
+                         break;
+                 case 'v':
+                         v_option = 1;
+                         break;
+                 case 'V':
+                         printf(_("eject version %s by Jeff Tranter (tranter@pobox.com)\n"), version);
+                         exit(0);
+                         break;
+                 case '?':
+                         exit(1);
+                         break;
+               }
+       }
+       /* check for a single additional argument */
+       if ((argc - optind) > 1) {
+               fprintf(stderr, _("%s: too many arguments\n"), programName);
+               exit(1);
+       }
+       if ((argc - optind) == 1) { /* one argument */
+               *device = strdup(argv[optind]);
+       }
+}
+
+
+/* Return 1 if file/device exists, 0 otherwise. */
+static int FileExists(const char *name)
+{
+
+       /*
+        * access() uses the UID, not the EUID. This way a normal user
+        * cannot find out if a file (say, /root/fubar) exists or not, even
+        * if eject is SUID root
+        */
+       if (access (name, F_OK) == 0) {
+               return 1;
+       }       else {
+               return 0;
+       }
+}
+
+
+/*
+ * Given name, such as foo, see if any of the following exist:
+ *
+ * foo (if foo starts with '.' or '/')
+ * /dev/foo
+ * /media/foo
+ * /mnt/foo
+ * /dev/cdroms/foo
+ * /dev/cdroms/foo0
+ * /dev/dsk/foo
+ * /dev/rdsk/foo
+ * ./foo
+ *
+ * If found, return the full path. If not found, return 0.
+ * Returns pointer to dynamically allocated string.
+ */
+static char *FindDevice(const char *name)
+{
+       char *buf;
+
+       buf = (char *) malloc(strlen(name)+14); /* to allow for "/dev/cdroms/ + "0" + null */
+       if (buf==NULL) {
+               fprintf(stderr, _("%s: could not allocate memory\n"), programName);
+               exit(1);
+       }
+       if ((name[0] == '.') || (name[0] == '/')) {
+               strcpy(buf, name);
+               if (FileExists(buf))
+                       return buf;
+       }
+
+       strcpy(buf, "/dev/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       strcpy(buf, "/media/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       strcpy(buf, "/mnt/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       /* for devfs under Linux */
+       strcpy(buf, "/dev/cdroms/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       strcpy(buf, "/dev/cdroms/");
+       strcat(buf, name);
+       strcat(buf, "0");
+       if (FileExists(buf))
+               return buf;
+
+       /* for devfs under Solaris */
+       strcpy(buf, "/dev/rdsk/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       strcpy(buf, "/dev/dsk/");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       strcpy(buf, "./");
+       strcat(buf, name);
+       if (FileExists(buf))
+               return buf;
+
+       free(buf);
+       buf = 0;
+       return 0;
+}
+
+
+/* Set or clear auto-eject mode. */
+static void AutoEject(int fd, int onOff)
+{
+       int status;
+
+       status = ioctl(fd, CDROMEJECT_SW, onOff);
+       if (status != 0) {
+               fprintf(stderr, _("%s: CD-ROM auto-eject command failed: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+}
+
+
+/*
+ * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
+ * CDROMLOADFROMSLOT.
+ */
+static void ChangerSelect(int fd, int slot)
+{
+       int status;
+
+#ifdef CDROM_SELECT_DISC
+       status = ioctl(fd, CDROM_SELECT_DISC, slot);
+       if (status < 0) {
+               fprintf(stderr, _("%s: CD-ROM select disc command failed: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+#elif defined CDROMLOADFROMSLOT
+       status = ioctl(fd, CDROMLOADFROMSLOT, slot);
+       if (status != 0) {
+               fprintf(stderr, _("%s: CD-ROM load from slot command failed: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+#else
+    fprintf(stderr, _("%s: IDE/ATAPI CD-ROM changer not supported by this kernel\n"), programName);
+#endif
+}
+
+
+/*
+ * Close tray. Not supported by older kernels.
+ */
+static void CloseTray(int fd)
+{
+       int status;
+
+#ifdef CDROMCLOSETRAY
+       status = ioctl(fd, CDROMCLOSETRAY);
+       if (status != 0) {
+               fprintf(stderr, _("%s: CD-ROM tray close command failed: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+#else
+    fprintf(stderr, _("%s: CD-ROM tray close command not supported by this kernel\n"), programName);
+#endif
+}
+
+/*
+ * Toggle tray.
+ *
+ * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and
+ * Sybren Stuvel <sybren@thirdtower.com>
+ *
+ * Not supported by older kernels because it might use
+ * CloseTray().
+ *
+ */
+static void ToggleTray(int fd)
+{
+       struct timeval time_start, time_stop;
+       int time_elapsed;
+
+#ifdef CDROMCLOSETRAY
+
+       /* Try to open the CDROM tray and measure the time therefor
+        * needed.  In my experience the function needs less than 0.05
+        * seconds if the tray was already open, and at least 1.5 seconds
+        * if it was closed.  */
+       gettimeofday(&time_start, NULL);
+
+       /* Send the CDROMEJECT command to the device. */
+       if (ioctl(fd, CDROMEJECT, 0) < 0) {
+               perror("ioctl");
+               exit(1);
+       }
+
+       /* Get the second timestamp, to measure the time needed to open
+        * the tray.  */
+       gettimeofday(&time_stop, NULL);
+
+       time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) -
+               (time_start.tv_sec * 1000000 + time_start.tv_usec);
+
+       /* If the tray "opened" too fast, we can be nearly sure, that it
+        * was already open. In this case, close it now. Else the tray was
+        * closed before. This would mean that we are done.  */
+       if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS)
+               CloseTray(fd);
+
+#else
+    fprintf(stderr, _("%s: CD-ROM tray toggle command not supported by this kernel\n"), programName);
+#endif
+
+}
+
+/*
+ * Select Speed of CD-ROM drive.
+ * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
+ * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
+ */
+static void SelectSpeedCdrom(int fd, int speed)
+{
+       int status;
+
+#ifdef CDROM_SELECT_SPEED
+       status = ioctl(fd, CDROM_SELECT_SPEED, speed);
+       if (status != 0) {
+               fprintf(stderr, _("%s: CD-ROM select speed command failed: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+#else
+    fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
+#endif
+}
+
+
+/*
+ * Eject using CDROMEJECT ioctl. Return 1 if successful, 0 otherwise.
+ */
+static int EjectCdrom(int fd)
+{
+       int status;
+
+       status = ioctl(fd, CDROMEJECT);
+       return (status == 0);
+}
+
+
+/*
+ * Eject using SCSI commands. Return 1 if successful, 0 otherwise.
+ */
+static int EjectScsi(int fd)
+{
+       int status;
+       struct sdata {
+               int  inlen;
+               int  outlen;
+               char cmd[256];
+       } scsi_cmd;
+
+       scsi_cmd.inlen  = 0;
+       scsi_cmd.outlen = 0;
+       scsi_cmd.cmd[0] = ALLOW_MEDIUM_REMOVAL;
+       scsi_cmd.cmd[1] = 0;
+       scsi_cmd.cmd[2] = 0;
+       scsi_cmd.cmd[3] = 0;
+       scsi_cmd.cmd[4] = 0;
+       scsi_cmd.cmd[5] = 0;
+       status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
+       if (status != 0)
+               return 0;
+
+       scsi_cmd.inlen  = 0;
+       scsi_cmd.outlen = 0;
+       scsi_cmd.cmd[0] = START_STOP;
+       scsi_cmd.cmd[1] = 0;
+       scsi_cmd.cmd[2] = 0;
+       scsi_cmd.cmd[3] = 0;
+       scsi_cmd.cmd[4] = 1;
+       scsi_cmd.cmd[5] = 0;
+       status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
+       if (status != 0)
+               return 0;
+
+       scsi_cmd.inlen  = 0;
+       scsi_cmd.outlen = 0;
+       scsi_cmd.cmd[0] = START_STOP;
+       scsi_cmd.cmd[1] = 0;
+       scsi_cmd.cmd[2] = 0;
+       scsi_cmd.cmd[3] = 0;
+       scsi_cmd.cmd[4] = 2;
+       scsi_cmd.cmd[5] = 0;
+       status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
+       if (status != 0)
+               return 0;
+
+       /* force kernel to reread partition table when new disc inserted */
+       status = ioctl(fd, BLKRRPART);
+       return (status == 0);
+}
+
+
+/*
+ * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
+ */
+static int EjectFloppy(int fd)
+{
+       int status;
+
+       status = ioctl(fd, FDEJECT);
+       return (status == 0);
+}
+
+
+/*
+ * Eject using tape ioctl. Return 1 if successful, 0 otherwise.
+ */
+static int EjectTape(int fd)
+{
+       int status;
+       struct mtop op;
+
+       op.mt_op = MTOFFL; /* rewind and eject */
+       op.mt_count = 0;   /* not used */
+       status = ioctl(fd, MTIOCTOP, &op);
+       return (status == 0);
+}
+
+
+/* Unmount a device. */
+static void Unmount(const char *fullName)
+{
+       int status;
+
+       switch (fork()) {
+         case 0: /* child */
+                 seteuid(getuid()); /* reduce likelyhood of security holes when running setuid */
+                 if(p_option)
+                         execl("/bin/umount", "/bin/umount", fullName, "-n", NULL);
+                 else
+                         execl("/bin/umount", "/bin/umount", fullName, NULL);
+                 fprintf(stderr, _("%s: unable to exec /bin/umount of `%s': %s\n"),
+                                 programName, fullName, strerror(errno));
+                 exit(1);
+                 break;
+         case -1:
+                 fprintf(stderr, _("%s: unable to fork: %s\n"), programName, strerror(errno));
+                 break;
+         default: /* parent */
+                 wait(&status);
+                 if (WIFEXITED(status) == 0) {
+                         fprintf(stderr, _("%s: unmount of `%s' did not exit normally\n"), programName, fullName);
+                         exit(1);
+                 }
+                 if (WEXITSTATUS(status) != 0) {
+                         fprintf(stderr, _("%s: unmount of `%s' failed\n"), programName, fullName);
+                         exit(1);
+                 }
+                 break;
+       }
+}
+
+
+/* Open a device file. */
+static int OpenDevice(const char *fullName)
+{
+       int fd = open(fullName, O_RDONLY|O_NONBLOCK);
+       if (fd == -1) {
+               fprintf(stderr, _("%s: unable to open `%s'\n"), programName, fullName);
+               exit(1);
+       }
+       return fd;
+}
+
+
+/*
+ * Get major and minor device numbers for a device file name, so we
+ * can check for duplicate devices.
+ */
+static int GetMajorMinor(const char *name, int *maj, int *min)
+{
+       struct stat sstat;
+       *maj = *min = -1;
+       if (stat(name, &sstat) == -1)
+               return -1;
+       if (! S_ISBLK(sstat.st_mode))
+               return -1;
+       *maj = major(sstat.st_rdev);
+       *min = minor(sstat.st_rdev);
+       return 0;
+}
+
+
+/*
+ * See if device has been mounted by looking in mount table.  If so, set
+ * device name and mount point name, and return 1, otherwise return 0.
+ */
+static int MountedDevice(const char *name, char **mountName, char **deviceName)
+{
+       FILE *fp;
+       char line[1024];
+       char s1[1024];
+       char s2[1024];
+       int rc;
+
+       int maj;
+       int min;
+
+       GetMajorMinor(name, &maj, &min);
+
+       fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
+       if (fp == NULL)
+       {
+               fprintf(stderr, _("unable to open %s: %s\n"), (p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
+               exit(1);
+       }
+
+       while (fgets(line, sizeof(line), fp) != 0) {
+               rc = sscanf(line, "%1023s %1023s", s1, s2);
+               if (rc >= 2) {
+                       int mtabmaj, mtabmin;
+                       GetMajorMinor(s1, &mtabmaj, &mtabmin);
+                       if (((strcmp(s1, name) == 0) || (strcmp(s2, name) == 0)) ||
+                               ((maj != -1) && (maj == mtabmaj) && (min == mtabmin))) {
+                               FCLOSE(fp);
+                               *deviceName = strdup(s1);
+                               *mountName = strdup(s2);
+                               return 1;
+                       }
+               }
+       }
+       *deviceName = 0;
+       *mountName = 0;
+       FCLOSE(fp);
+       return 0;
+}
+
+
+/*
+ * See if device can be mounted by looking in /etc/fstab.
+ * If so, set device name and mount point name, and return 1,
+ * otherwise return 0.
+ */
+static int MountableDevice(const char *name, char **mountName, char **deviceName)
+{
+       FILE *fp;
+       char line[1024];
+       char s1[1024];
+       char s2[1024];
+       int rc;
+
+       fp = fopen("/etc/fstab", "r");
+       if (fp == NULL) {
+/*
+ * /etc/fstab may be unreadable in some situations due to passwords in the
+ * file.
+ */
+/*             fprintf(stderr, _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
+               exit(1);*/
+               if (v_option) {
+                       printf( _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
+               }
+               return -1;
+       }
+
+       while (fgets(line, sizeof(line), fp) != 0) {
+               rc = sscanf(line, "%1023s %1023s", s1, s2);
+               if (rc >= 2 && s1[0] != '#' && strcmp(s2, name) == 0) {
+                       FCLOSE(fp);
+                       *deviceName = strdup(s1);
+                       *mountName = strdup(s2);
+                       return 1;
+               }
+       }
+       FCLOSE(fp);
+       return 0;
+}
+
+
+/*
+ * Step through mount table and unmount all devices that match a regular
+ * expression.
+ */
+static void UnmountDevices(const char *pattern)
+{
+       regex_t preg;
+       FILE *fp;
+       char s1[1024];
+       char s2[1024];
+       char line[1024];
+       int status;
+
+       if (regcomp(&preg, pattern, REG_EXTENDED)!=0) {
+               perror(programName);
+               exit(1);
+       }
+
+       fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
+       if (fp == NULL)
+       {
+               fprintf(stderr, _("unable to open %s: %s\n"),(p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
+               exit(1);
+       }
+
+       while (fgets(line, sizeof(line), fp) != 0) {
+               status = sscanf(line, "%1023s %1023s", s1, s2);
+               if (status >= 2) {
+                       status = regexec(&preg, s1, 0, 0, 0);
+                       if (status == 0) {
+                               if (v_option)
+                                       printf(_("%s: unmounting `%s'\n"), programName, s1);
+                               Unmount(s1);
+                               regfree(&preg);
+                       }
+               }
+       }
+       FCLOSE(fp);
+}
+
+
+/* Check if name is a symbolic link. If so, return what it points to. */
+static char *SymLink(const char *name)
+{
+       int status;
+       char s1[PATH_MAX];
+       char s2[PATH_MAX];
+       char s4[PATH_MAX];
+       char result[PATH_MAX];
+       char *s3;
+
+       memset(s1, 0, sizeof(s1));
+       memset(s2, 0, sizeof(s2));
+       memset(s4, 0, sizeof(s4));
+       memset(result, 0, sizeof(result));
+
+       status = readlink(name, s1, sizeof(s1) - 1);
+
+       if (status == -1)
+               return 0;
+
+       s1[status] = 0;
+       if (s1[0] == '/') { /* absolute link */
+               return strdup(s1);
+       } else { /* relative link, add base name */
+               strncpy(s2, name, sizeof(s2)-1);
+               s3 = strrchr(s2, '/');
+               if (s3 != 0) {
+                       s3[1] = 0;
+                       snprintf(result, sizeof(result)-1, "%s%s", s2, s1);
+               }
+       }
+       realpath(result, s4);
+       return strdup(s4);
+}
+
+
+/*
+ * Given a name, see if it matches a pattern for a device that can have
+ * multiple partitions.  If so, return a regular expression that matches
+ * partitions for that device, otherwise return 0.
+ */
+static char *MultiplePartitions(const char *name)
+{
+       int i = 0;
+       int status;
+       regex_t preg;
+       char pattern[256];
+       char *result = 0;
+
+       for (i = 0; partitionDevice[i] != 0; i++) {
+               /* look for ^/dev/foo[a-z]([0-9]?[0-9])?$, e.g. /dev/hda1 */
+               strcpy(pattern, "^/dev/");
+               strcat(pattern, partitionDevice[i]);
+               strcat(pattern, "[a-z]([0-9]?[0-9])?$");
+               regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB);
+               status = regexec(&preg, name, 1, 0, 0);
+               regfree(&preg);
+               if (status == 0) {
+                       result = (char *) malloc(strlen(name) + 25);
+                       strcpy(result, name);
+                       result[strlen(partitionDevice[i]) + 6] = 0;
+                       strcat(result, "([0-9]?[0-9])?$");
+                       if (v_option)
+                               printf(_("%s: `%s' is a multipartition device\n"), programName, name);
+                       return result;
+               }
+       }
+       if (v_option)
+               printf(_("%s: `%s' is not a multipartition device\n"), programName, name);
+       return 0;
+}
+
+
+/* handle -x option */
+void HandleXOption(char *deviceName)
+{
+       int fd;            /* file descriptor for device */
+       if (x_option) {
+               if (v_option)
+               {
+                       if (x_arg == 0)
+                               printf(_("%s: setting CD-ROM speed to auto\n"), programName);
+                       else
+                               printf(_("%s: setting CD-ROM speed to %dX\n"), programName, x_arg);
+               }
+               fd = OpenDevice(deviceName);
+               SelectSpeedCdrom(fd, x_arg);
+               exit(0);
+       }
+}
+
+
+/* main program */
+int main(int argc, char **argv)
+{
+       const char *defaultDevice = DEFAULTDEVICE;  /* default if no name passed by user */
+       int worked = 0;    /* set to 1 when successfully ejected */
+       char *device = 0;  /* name passed from user */
+       char *fullName;    /* expanded name */
+       char *deviceName;  /* name of device */
+       char *linkName;    /* name of device's symbolic link */
+       char *mountName;   /* name of device's mount point */
+       int fd;            /* file descriptor for device */
+       int mounted = 0;   /* true if device is mounted */
+       int mountable = 0; /* true if device is in /etc/fstab */
+       char *pattern;     /* regex for device if multiple partitions */
+       int ld = 6;        /* symbolic link max depth */
+
+       I18NCODE
+
+       /* program name is global variable used by other procedures */
+       programName = strdup(argv[0]);
+
+       /* parse the command line arguments */
+       parse_args(argc, argv, &device);
+
+
+       /* handle -d option */
+       if (d_option) {
+               printf(_("%s: default device: `%s'\n"), programName, defaultDevice);
+               exit(0);
+       }
+
+       /* if no device, use default */
+       if (device == 0) {
+               device = strdup(defaultDevice);
+               if (v_option)
+                       printf(_("%s: using default device `%s'\n"), programName, device);
+       }
+
+       /* Strip any trailing slash from name in case user used bash/tcsh
+          style filename completion (e.g. /mnt/cdrom/) */
+       if (device[strlen(device)-1] == '/')
+               device[strlen(device)-1] = 0;
+
+       if (v_option)
+               printf(_("%s: device name is `%s'\n"), programName, device);
+
+       /* figure out full device or mount point name */
+       fullName = FindDevice(device);
+       if (fullName == 0) {
+               fprintf(stderr, _("%s: unable to find or open device for: `%s'\n"), programName, device);
+               exit(1);
+       }
+       if (v_option)
+               printf(_("%s: expanded name is `%s'\n"), programName, fullName);
+
+       /* check for a symbolic link */
+       while ((linkName = SymLink(fullName)) && (ld > 0)) {
+               if (v_option)
+                       printf(_("%s: `%s' is a link to `%s'\n"), programName, fullName, linkName);
+               free(fullName);
+               fullName = strdup(linkName);
+               free(linkName);
+               linkName = 0;
+               ld--;
+       }
+       /* handle max depth exceeded option */
+       if (ld <= 0) {
+               printf(_("%s: maximum symbolic link depth exceeded: `%s'\n"), programName, fullName);
+               exit(1);
+       }
+
+       /* if mount point, get device name */
+       mounted = MountedDevice(fullName, &mountName, &deviceName);
+       if (v_option) {
+               if (mounted)
+                       printf(_("%s: `%s' is mounted at `%s'\n"), programName, deviceName, mountName);
+               else
+                       printf(_("%s: `%s' is not mounted\n"), programName, fullName);
+       }
+       if (!mounted) {
+               deviceName = strdup(fullName);
+       }
+
+       /* if not currently mounted, see if it is a possible mount point */
+       if (!mounted) {
+               mountable = MountableDevice(fullName, &mountName, &deviceName);
+               /* if return value -1 then fstab could not be read */
+               if (v_option && mountable >= 0) {
+                       if (mountable)
+                               printf(_("%s: `%s' can be mounted at `%s'\n"), programName, deviceName, mountName);
+                       else
+                               printf(_("%s: `%s' is not a mount point\n"), programName, fullName);
+               }
+       }
+
+       /* handle -n option */
+       if (n_option) {
+               printf(_("%s: device is `%s'\n"), programName, deviceName);
+               if (v_option)
+                       printf(_("%s: exiting due to -n/--noop option\n"), programName);
+               exit(0);
+       }
+
+       /* handle -a option */
+       if (a_option) {
+               if (v_option) {
+                       if (a_arg)
+                               printf(_("%s: enabling auto-eject mode for `%s'\n"), programName, deviceName);
+                       else
+                               printf(_("%s: disabling auto-eject mode for `%s'\n"), programName, deviceName);
+               }
+               fd = OpenDevice(deviceName);
+               AutoEject(fd, a_arg);
+               exit(0);
+       }
+
+       /* handle -t option */
+       if (t_option) {
+               if (v_option)
+                       printf(_("%s: closing tray\n"), programName);
+               fd = OpenDevice(deviceName);
+               CloseTray(fd);
+               HandleXOption(deviceName);
+               exit(0);
+       }
+
+       /* handle -T option */
+       if (T_option) {
+               if (v_option)
+                       printf(_("%s: toggling tray\n"), programName);
+               fd = OpenDevice(deviceName);
+               ToggleTray(fd);
+               HandleXOption(deviceName);
+               exit(0);
+       }
+
+       /* handle -x option only */
+       if (!c_option) HandleXOption(deviceName);
+
+       /* unmount device if mounted */
+       if ((m_option != 1) && mounted) {
+               if (v_option)
+                       printf(_("%s: unmounting `%s'\n"), programName, deviceName);
+               Unmount(deviceName);
+       }
+
+       /* if it is a multipartition device, unmount any other partitions on
+          the device */
+       pattern = MultiplePartitions(deviceName);
+       if ((m_option != 1) && (pattern != 0))
+               UnmountDevices(pattern);
+
+       /* handle -c option */
+       if (c_option) {
+               if (v_option)
+                       printf(_("%s: selecting CD-ROM disc #%d\n"), programName, c_arg);
+               fd = OpenDevice(deviceName);
+               ChangerSelect(fd, c_arg);
+               HandleXOption(deviceName);
+               exit(0);
+       }
+
+       /* if user did not specify type of eject, try all four methods */
+       if ((r_option + s_option + f_option + q_option) == 0) {
+               r_option = s_option = f_option = q_option = 1;
+       }
+
+       /* open device */
+       fd = OpenDevice(deviceName);
+
+       /* try various methods of ejecting until it works */
+       if (r_option) {
+               if (v_option)
+                       printf(_("%s: trying to eject `%s' using CD-ROM eject command\n"), programName, deviceName);
+               worked = EjectCdrom(fd);
+               if (v_option) {
+                       if (worked)
+                               printf(_("%s: CD-ROM eject command succeeded\n"), programName);
+                       else
+                               printf(_("%s: CD-ROM eject command failed\n"), programName);
+               }
+       }
+
+       if (s_option && !worked) {
+               if (v_option)
+                       printf(_("%s: trying to eject `%s' using SCSI commands\n"), programName, deviceName);
+               worked = EjectScsi(fd);
+               if (v_option) {
+                       if (worked)
+                               printf(_("%s: SCSI eject succeeded\n"), programName);
+                       else
+                               printf(_("%s: SCSI eject failed\n"), programName);
+               }
+       }
+
+       if (f_option && !worked) {
+               if (v_option)
+                       printf(_("%s: trying to eject `%s' using floppy eject command\n"), programName, deviceName);
+               worked = EjectFloppy(fd);
+               if (v_option) {
+                       if (worked)
+                               printf(_("%s: floppy eject command succeeded\n"), programName);
+                       else
+                               printf(_("%s: floppy eject command failed\n"), programName);
+               }
+       }
+
+       if (q_option && !worked) {
+               if (v_option)
+                       printf(_("%s: trying to eject `%s' using tape offline command\n"), programName, deviceName);
+               worked = EjectTape(fd);
+               if (v_option) {
+                       if (worked)
+                               printf(_("%s: tape offline command succeeded\n"), programName);
+                       else
+                               printf(_("%s: tape offline command failed\n"), programName);
+               }
+       }
+
+       if (!worked) {
+               fprintf(stderr, _("%s: unable to eject, last error: %s\n"), programName, strerror(errno));
+               exit(1);
+       }
+
+       /* cleanup */
+       CLOSE(fd);
+       free(device);
+       free(deviceName);
+       free(fullName);
+       free(linkName);
+       free(mountName);
+       free(pattern);
+       exit(0);
+}
diff --git a/misc-utils/i18n.h b/misc-utils/i18n.h
new file mode 100644 (file)
index 0000000..e4c821b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+    i18nized by:  KUN-CHUNG, HSIEH <linuxer@coventive.com>
+                 Taiwan
+
+    Homepage: http://www.geocities.com/linux4tw/
+
+    µ{¦¡°ê»Ú¤Æ³]­p:  Á±X¤¤
+*/
+
+#ifndef __i18n__
+   #define __i18n__
+   #define PKG "eject"
+   #define LOCALEDIR "/usr/share/locale"
+
+   #include <locale.h>
+   #include <libintl.h>
+   #define _(str) gettext (str)
+   #define N_(str) (str)
+   #define I18NCODE setlocale(LC_ALL,""); textdomain(PKG); bindtextdomain(PKG,LOCALEDIR);
+
+   void i18n_init (void);
+#endif