#include <stdio.h>
#include <stdlib.h>
+#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <locale.h>
+#include <langinfo.h>
+#include <termios.h>
#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#include "closeout.h"
#include "progname.h"
+/* List of drives added via -a, -d or -N options. */
struct drv {
struct drv *next;
- char *filename; /* disk filename (for -a or -N options) */
- prep_data *data; /* prepared type (for -N option only) */
- char *device; /* device inside the appliance */
+ enum { drv_a, drv_d, drv_N } type;
+ union {
+ struct {
+ char *filename; /* disk filename */
+ } a;
+ struct {
+ char *guest; /* guest name */
+ } d;
+ struct {
+ char *filename; /* disk filename (testX.img) */
+ prep_data *data; /* prepared type */
+ char *device; /* device inside the appliance */
+ } N;
+ };
};
struct mp {
char *mountpoint;
};
-static void add_drives (struct drv *drv);
+static void set_up_terminal (void);
+static char add_drives (struct drv *drv, char next_drive);
static void prepare_drives (struct drv *drv);
static void mount_mps (struct mp *mp);
static int launch (void);
#ifdef HAVE_LIBREADLINE
static void add_history_line (const char *);
#endif
-static void print_shell_quote (FILE *stream, const char *str);
+
+static int override_progress_bars = -1;
/* Currently open libguestfs handle. */
guestfs_h *g;
int remote_control = 0;
int exit_on_error = 1;
int command_num = 0;
+int keys_from_stdin = 0;
+const char *libvirt_uri = NULL;
+int inspector = 0;
+int utf8_mode = 0;
+int have_terminfo = 0;
+int progress_bars = 0;
static void __attribute__((noreturn))
usage (int status)
" -h|--cmd-help List available commands\n"
" -h|--cmd-help cmd Display detailed help on 'cmd'\n"
" -a|--add image Add image\n"
+ " -c|--connect uri Specify libvirt URI for -d option\n"
+ " -d|--domain guest Add disks from libvirt guest\n"
" -D|--no-dest-paths Don't tab-complete paths from guest fs\n"
" -f|--file file Read commands from file\n"
- " -i|--inspector Run virt-inspector to get disk mountpoints\n"
+ " -i|--inspector Automatically mount filesystems\n"
+ " --keys-from-stdin Read passphrases from stdin\n"
" --listen Listen for remote commands\n"
" -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
" -n|--no-sync Don't autosync\n"
" -N|--new type Create prepared disk (test1.img, ...)\n"
+ " --progress-bars Enable progress bars even when not interactive\n"
+ " --no-progress-bars Disable progress bars\n"
" --remote[=pid] Send commands to remote %s\n"
" -r|--ro Mount read-only\n"
" --selinux Enable SELinux support\n"
bindtextdomain (PACKAGE, LOCALEBASEDIR);
textdomain (PACKAGE);
+ set_up_terminal ();
+
enum { HELP_OPTION = CHAR_MAX + 1 };
- static const char *options = "a:Df:h::im:nN:rv?Vx";
+ static const char *options = "a:c:d:Df:h::im:nN:rv?Vx";
static const struct option long_options[] = {
{ "add", 1, 0, 'a' },
{ "cmd-help", 2, 0, 'h' },
+ { "connect", 1, 0, 'c' },
+ { "domain", 1, 0, 'd' },
{ "file", 1, 0, 'f' },
{ "help", 0, 0, HELP_OPTION },
{ "inspector", 0, 0, 'i' },
+ { "keys-from-stdin", 0, 0, 0 },
{ "listen", 0, 0, 0 },
{ "mount", 1, 0, 'm' },
{ "new", 1, 0, 'N' },
{ "no-dest-paths", 0, 0, 'D' },
{ "no-sync", 0, 0, 'n' },
+ { "progress-bars", 0, 0, 0 },
+ { "no-progress-bars", 0, 0, 0 },
{ "remote", 2, 0, 0 },
{ "ro", 0, 0, 'r' },
{ "selinux", 0, 0, 0 },
struct mp *mp;
char *p, *file = NULL;
int c;
- int inspector = 0;
int option_index;
struct sigaction sa;
- char next_drive = 'a';
int next_prepared_drive = 1;
initialize_readline ();
* using it just above.
*
* getopt_long uses argv[0], so give it the sanitized name. Save a copy
- * of the original, in case it's needed in virt-inspector mode, below.
+ * of the original, in case it's needed below.
*/
char *real_argv0 = argv[0];
argv[0] = bad_cast (program_name);
}
} else if (STREQ (long_options[option_index].name, "selinux")) {
guestfs_set_selinux (g, 1);
+ } else if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
+ keys_from_stdin = 1;
+ } else if (STREQ (long_options[option_index].name, "progress-bars")) {
+ override_progress_bars = 1;
+ } else if (STREQ (long_options[option_index].name, "no-progress-bars")) {
+ override_progress_bars = 0;
} else {
fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
program_name, long_options[option_index].name, option_index);
perror ("malloc");
exit (EXIT_FAILURE);
}
- drv->filename = optarg;
- drv->data = NULL;
- /* We could fill the device field in, but in fact we
- * only use it for the -N option at present.
- */
- drv->device = NULL;
+ drv->type = drv_a;
+ drv->a.filename = optarg;
+ drv->next = drvs;
+ drvs = drv;
+ break;
+
+ case 'c':
+ libvirt_uri = optarg;
+ break;
+
+ case 'd':
+ drv = malloc (sizeof (struct drv));
+ if (!drv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ drv->type = drv_d;
+ drv->d.guest = optarg;
drv->next = drvs;
drvs = drv;
- next_drive++;
break;
case 'N':
- if (STRCASEEQ (optarg, "list")) {
+ if (STRCASEEQ (optarg, "list") ||
+ STRCASEEQ (optarg, "help") ||
+ STRCASEEQ (optarg, "h") ||
+ STRCASEEQ (optarg, "?")) {
list_prepared_drives ();
exit (EXIT_SUCCESS);
}
perror ("malloc");
exit (EXIT_FAILURE);
}
- if (asprintf (&drv->filename, "test%d.img",
+ drv->type = drv_N;
+ if (asprintf (&drv->N.filename, "test%d.img",
next_prepared_drive++) == -1) {
perror ("asprintf");
exit (EXIT_FAILURE);
}
- drv->data = create_prepared_file (optarg, drv->filename);
- if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) {
- perror ("asprintf");
- exit (EXIT_FAILURE);
- }
+ drv->N.data = create_prepared_file (optarg, drv->N.filename);
+ drv->N.device = NULL; /* filled in by add_drives */
drv->next = drvs;
drvs = drv;
break;
file = optarg;
break;
- case 'h':
+ case 'h': {
+ int r = 0;
+
if (optarg)
- display_command (optarg);
+ r = display_command (optarg);
else if (argv[optind] && argv[optind][0] != '-')
- display_command (argv[optind++]);
+ r = display_command (argv[optind++]);
else
list_commands ();
- exit (EXIT_SUCCESS);
+
+ exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
case 'i':
inspector = 1;
guestfs_set_verbose (g, verbose);
break;
- case 'V':
- printf ("%s %s\n", program_name, PACKAGE_VERSION);
+ case 'V': {
+ struct guestfs_version *v = guestfs_version (g);
+ printf ("%s %"PRIi64".%"PRIi64".%"PRIi64"%s\n", program_name,
+ v->major, v->minor, v->release, v->extra);
exit (EXIT_SUCCESS);
+ }
case 'x':
guestfs_set_trace (g, 1);
}
}
- /* Inspector mode invalidates most of the other arguments. */
- if (inspector) {
- if (drvs || mps || remote_control_listen || remote_control ||
- guestfs_get_selinux (g)) {
- fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, "
- "--listen, --remote or --selinux\n"),
- program_name);
- exit (EXIT_FAILURE);
- }
- if (optind >= argc) {
- fprintf (stderr,
- _("%s: -i requires a libvirt domain or path(s) to disk image(s)\n"),
- program_name);
- exit (EXIT_FAILURE);
- }
-
- char *cmd;
- size_t cmdlen;
- FILE *fp = open_memstream (&cmd, &cmdlen);
- if (fp == NULL) {
- perror ("open_memstream");
- exit (EXIT_FAILURE);
- }
-
- fprintf (fp, "virt-inspector");
+ /* Old-style -i syntax? Since -a/-d/-N and -i was disallowed
+ * previously, if we have -i without any drives but with something
+ * on the command line, it must be old-style syntax.
+ */
+ if (inspector && drvs == NULL && optind < argc) {
while (optind < argc) {
- fputc (' ', fp);
- print_shell_quote (fp, argv[optind]);
- optind++;
- }
-
- if (read_only)
- fprintf (fp, " --ro-fish");
- else
- fprintf (fp, " --fish");
-
- if (fclose (fp) == -1) {
- perror ("fclose");
- exit (EXIT_FAILURE);
- }
-
- if (verbose)
- fprintf (stderr,
- "%s -i: running: %s\n", program_name, cmd);
-
- FILE *pp = popen (cmd, "r");
- if (pp == NULL) {
- perror (cmd);
- exit (EXIT_FAILURE);
- }
-
- char *cmd2;
- fp = open_memstream (&cmd2, &cmdlen);
- if (fp == NULL) {
- perror ("open_memstream");
- exit (EXIT_FAILURE);
- }
-
- fprintf (fp, "%s", real_argv0);
-
- if (guestfs_get_verbose (g))
- fprintf (fp, " -v");
- if (!guestfs_get_autosync (g))
- fprintf (fp, " -n");
- if (guestfs_get_trace (g))
- fprintf (fp, " -x");
-
- char *insp = NULL;
- size_t insplen;
- if (getline (&insp, &insplen, pp) == -1) {
- perror (cmd);
- exit (EXIT_FAILURE);
- }
- fprintf (fp, " %s", insp);
-
- if (pclose (pp) == -1) {
- perror (cmd);
- exit (EXIT_FAILURE);
- }
-
- if (fclose (fp) == -1) {
- perror ("fclose");
- exit (EXIT_FAILURE);
- }
-
- if (verbose)
- fprintf (stderr,
- "%s -i: running: %s\n", program_name, cmd2);
+ if (strchr (argv[optind], '/') ||
+ access (argv[optind], F_OK) == 0) { /* simulate -a option */
+ drv = malloc (sizeof (struct drv));
+ if (!drv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ drv->type = drv_a;
+ drv->a.filename = argv[optind];
+ drv->next = drvs;
+ drvs = drv;
+ } else { /* simulate -d option */
+ drv = malloc (sizeof (struct drv));
+ if (!drv) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ drv->type = drv_d;
+ drv->d.guest = argv[optind];
+ drv->next = drvs;
+ drvs = drv;
+ }
- int r = system (cmd2);
- if (r == -1) {
- perror (cmd2);
- exit (EXIT_FAILURE);
+ optind++;
}
-
- free (cmd);
- free (cmd2);
- free (insp);
-
- exit (WEXITSTATUS (r));
}
/* If we've got drives to add, add them now. */
- add_drives (drvs);
+ add_drives (drvs, 'a');
- /* If we've got mountpoints or prepared drives, we must launch the
- * guest and mount them.
+ /* If we've got mountpoints or prepared drives or -i option, we must
+ * launch the guest and mount them.
*/
- if (next_prepared_drive > 1 || mps != NULL) {
+ if (next_prepared_drive > 1 || mps != NULL || inspector) {
+ /* RHBZ#612178: If --listen flag is given, then we will fork into
+ * the background in rc_listen(). However you can't do this while
+ * holding a libguestfs handle open because the recovery process
+ * will think the main program has died and kill qemu. Therefore
+ * don't use the recovery process for this case. (A better
+ * solution would be to call launch () etc after the fork, but
+ * that greatly complicates the code here).
+ */
+ if (remote_control_listen)
+ guestfs_set_recovery_proc (g, 0);
+
if (launch () == -1) exit (EXIT_FAILURE);
+
+ if (inspector)
+ inspect_mount ();
+
prepare_drives (drvs);
mount_mps (mps);
}
}
}
+ /* Decide if we display progress bars. */
+ progress_bars =
+ override_progress_bars >= 0
+ ? override_progress_bars
+ : (optind >= argc && isatty (0));
+
+ if (progress_bars)
+ guestfs_set_progress_callback (g, progress_callback, NULL);
+
/* Interactive, shell script, or command(s) on the command line? */
if (optind >= argc) {
if (isatty (0))
exit (EXIT_SUCCESS);
}
+/* The <term.h> header file which defines this has "issues". */
+extern int tgetent (char *, const char *);
+
+static void
+set_up_terminal (void)
+{
+ /* http://www.cl.cam.ac.uk/~mgk25/unicode.html#activate */
+ utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8");
+
+ char *term = getenv ("TERM");
+ if (term == NULL) {
+ //fprintf (stderr, _("guestfish: TERM (terminal type) not defined.\n"));
+ return;
+ }
+
+ int r = tgetent (NULL, term);
+ if (r == -1) {
+ fprintf (stderr, _("guestfish: could not access termcap or terminfo database.\n"));
+ return;
+ }
+ if (r == 0) {
+ fprintf (stderr, _("guestfish: terminal type \"%s\" not defined.\n"),
+ term);
+ return;
+ }
+
+ have_terminfo = 1;
+}
+
void
pod2text (const char *name, const char *shortdesc, const char *str)
{
}
}
-static void
-add_drives (struct drv *drv)
+static char
+add_drives (struct drv *drv, char next_drive)
{
int r;
+ if (next_drive > 'z') {
+ fprintf (stderr,
+ _("guestfish: too many drives added on the command line\n"));
+ exit (EXIT_FAILURE);
+ }
+
if (drv) {
- add_drives (drv->next);
+ next_drive = add_drives (drv->next, next_drive);
- if (drv->data /* -N option is not affected by --ro */ || !read_only)
- r = guestfs_add_drive (g, drv->filename);
- else
- r = guestfs_add_drive_ro (g, drv->filename);
- if (r == -1)
- exit (EXIT_FAILURE);
+ switch (drv->type) {
+ case drv_a:
+ if (!read_only)
+ r = guestfs_add_drive (g, drv->a.filename);
+ else
+ r = guestfs_add_drive_ro (g, drv->a.filename);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ next_drive++;
+ break;
+
+ case drv_d:
+ r = add_libvirt_drives (drv->d.guest);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ next_drive += r;
+ break;
+
+ case drv_N:
+ /* -N option is not affected by --ro */
+ r = guestfs_add_drive (g, drv->N.filename);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ if (asprintf (&drv->N.device, "/dev/sd%c", next_drive) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+
+ next_drive++;
+ break;
+
+ default: /* keep GCC happy */
+ abort ();
+ }
}
+
+ return next_drive;
}
static void
{
if (drv) {
prepare_drives (drv->next);
- if (drv->data)
- prepare_drive (drv->filename, drv->data, drv->device);
+ if (drv->type == drv_N)
+ prepare_drive (drv->N.filename, drv->N.data, drv->N.device);
}
}
int global_exit_on_error = !prompt;
int tilde_candidate;
- if (prompt)
+ if (prompt) {
printf (_("\n"
"Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
"editing virtual machine filesystems.\n"
" 'quit' to quit the shell\n"
"\n"));
+ if (inspector) {
+ print_inspect_prompt ();
+ printf ("\n");
+ }
+ }
+
while (!quit) {
char *pipe = NULL;
int pid = 0;
int i, r;
+ reset_progress_bar ();
+
/* This counts the commands issued, starting at 1. */
command_num++;
/* Otherwise execute it locally. */
else if (STRCASEEQ (cmd, "help")) {
- if (argc == 0)
+ if (argc == 0) {
list_commands ();
- else
- display_command (argv[0]);
- r = 0;
+ r = 0;
+ } else
+ r = display_command (argv[0]);
}
else if (STRCASEEQ (cmd, "quit") ||
STRCASEEQ (cmd, "exit") ||
else if (STRCASEEQ (cmd, "alloc") ||
STRCASEEQ (cmd, "allocate"))
r = do_alloc (cmd, argc, argv);
+ else if (STRCASEEQ (cmd, "copy-in") ||
+ STRCASEEQ (cmd, "copy_in"))
+ r = do_copy_in (cmd, argc, argv);
+ else if (STRCASEEQ (cmd, "copy-out") ||
+ STRCASEEQ (cmd, "copy_out"))
+ r = do_copy_out (cmd, argc, argv);
else if (STRCASEEQ (cmd, "echo"))
r = do_echo (cmd, argc, argv);
else if (STRCASEEQ (cmd, "edit") ||
printf ("%-20s %s\n",
"alloc", _("allocate an image"));
printf ("%-20s %s\n",
+ "copy-in", _("copy files into an image"));
+ printf ("%-20s %s\n",
+ "copy-out", _("copy files out of an image"));
+ printf ("%-20s %s\n",
"echo", _("display a line of text"));
printf ("%-20s %s\n",
"edit", _("edit a file in the image"));
/* actions are printed after this (see list_commands) */
}
-void
+int
display_builtin_command (const char *cmd)
{
/* help for actions is auto-generated, see display_command */
if (STRCASEEQ (cmd, "alloc") ||
- STRCASEEQ (cmd, "allocate"))
+ STRCASEEQ (cmd, "allocate")) {
printf (_("alloc - allocate an image\n"
" alloc <filename> <size>\n"
"\n"
"\n"
" Size can be specified using standard suffixes, eg. '1M'.\n"
));
- else if (STRCASEEQ (cmd, "echo"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "copy-in") ||
+ STRCASEEQ (cmd, "copy_in")) {
+ printf (_("copy-in - copy files into an image\n"
+ " copy-in <local> [<local> ...] <remotedir>\n"
+ "\n"
+ " Copy local files or directories recursively into the\n"
+ " image, placing them on a remote directory.\n"
+ ));
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "copy-out") ||
+ STRCASEEQ (cmd, "copy_out")) {
+ printf (_("copy-out - copy files out of an image\n"
+ " copy-out <remote> [<remote> ...] <localdir>\n"
+ "\n"
+ " Copy remote files or directories recursively out of the\n"
+ " image, placing them in a local directory.\n"
+ ));
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "echo")) {
printf (_("echo - display a line of text\n"
" echo [<params> ...]\n"
"\n"
" This echos the parameters to the terminal.\n"));
+ return 0;
+ }
else if (STRCASEEQ (cmd, "edit") ||
STRCASEEQ (cmd, "vi") ||
- STRCASEEQ (cmd, "emacs"))
+ STRCASEEQ (cmd, "emacs")) {
printf (_("edit - edit a file in the image\n"
" edit <filename>\n"
"\n"
"\n"
" NOTE: This will not work reliably for large files\n"
" (> 2 MB) or binary files containing \\0 bytes.\n"));
- else if (STRCASEEQ (cmd, "lcd"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "lcd")) {
printf (_("lcd - local change directory\n"
" lcd <directory>\n"
"\n"
" Change guestfish's current directory. This command is\n"
" useful if you want to download files to a particular\n"
" place.\n"));
- else if (STRCASEEQ (cmd, "glob"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "glob")) {
printf (_("glob - expand wildcards in command\n"
" glob <command> [<args> ...]\n"
"\n"
" Glob runs <command> with wildcards expanded in any\n"
" command args. Note that the command is run repeatedly\n"
" once for each expanded argument.\n"));
+ return 0;
+ }
else if (STRCASEEQ (cmd, "man") ||
- STRCASEEQ (cmd, "manual"))
+ STRCASEEQ (cmd, "manual")) {
printf (_("man - read the manual\n"
" man\n"
"\n"
" Opens the manual page for guestfish.\n"));
- else if (STRCASEEQ (cmd, "help"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "help")) {
printf (_("help - display a list of commands or help on a command\n"
" help cmd\n"
" help\n"));
+ return 0;
+ }
else if (STRCASEEQ (cmd, "more") ||
- STRCASEEQ (cmd, "less"))
+ STRCASEEQ (cmd, "less")) {
printf (_("more - view a file in the pager\n"
" more <filename>\n"
"\n"
"\n"
" NOTE: This will not work reliably for large files\n"
" (> 2 MB) or binary files containing \\0 bytes.\n"));
+ return 0;
+ }
else if (STRCASEEQ (cmd, "quit") ||
STRCASEEQ (cmd, "exit") ||
- STRCASEEQ (cmd, "q"))
+ STRCASEEQ (cmd, "q")) {
printf (_("quit - quit guestfish\n"
" quit\n"));
- else if (STRCASEEQ (cmd, "reopen"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "reopen")) {
printf (_("reopen - close and reopen the libguestfs handle\n"
" reopen\n"
"\n"
"Close and reopen the libguestfs handle. It is not necessary to use\n"
"this normally, because the handle is closed properly when guestfish\n"
"exits. However this is occasionally useful for testing.\n"));
- else if (STRCASEEQ (cmd, "sparse"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "sparse")) {
printf (_("sparse - allocate a sparse image file\n"
" sparse <filename> <size>\n"
"\n"
"\n"
" Size can be specified using standard suffixes, eg. '1M'.\n"
));
- else if (STRCASEEQ (cmd, "supported"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "supported")) {
printf (_("supported - list supported groups of commands\n"
" supported\n"
"\n"
"\n"
" See also guestfs(3) section AVAILABILITY.\n"
));
- else if (STRCASEEQ (cmd, "time"))
+ return 0;
+ }
+ else if (STRCASEEQ (cmd, "time")) {
printf (_("time - measure time taken to run command\n"
" time <command> [<args> ...]\n"
"\n"
" This runs <command> as usual, and prints the elapsed\n"
" time afterwards.\n"));
- else
+ return 0;
+ }
+ else {
fprintf (stderr, _("%s: command not known, use -h to list all commands\n"),
cmd);
+ return -1;
+ }
}
/* This is printed when the user types in an unknown command for the
return ret;
}
-static void
-print_shell_quote (FILE *stream, const char *str)
+/* Read a passphrase ('Key') from /dev/tty with echo off.
+ * The caller (cmds.c) will call free on the string afterwards.
+ * Based on the code in cryptsetup file lib/utils.c.
+ */
+char *
+read_key (const char *param)
{
-#define SAFE(c) (c_isalnum((c)) || \
- (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.')
- int i;
+ FILE *infp, *outfp;
+ struct termios orig, temp;
+ char *ret = NULL;
+
+ /* Read and write to /dev/tty if available. */
+ if (keys_from_stdin ||
+ (infp = outfp = fopen ("/dev/tty", "w+")) == NULL) {
+ infp = stdin;
+ outfp = stdout;
+ }
- for (i = 0; str[i]; ++i) {
- if (!SAFE(str[i]))
- putc ('\\', stream);
- putc (str[i], stream);
+ /* Print the prompt and set no echo. */
+ int tty = isatty (fileno (infp));
+ int tcset = 0;
+ if (tty) {
+ fprintf (outfp, _("Enter key or passphrase (\"%s\"): "), param);
+
+ if (tcgetattr (fileno (infp), &orig) == -1) {
+ perror ("tcgetattr");
+ goto error;
+ }
+ memcpy (&temp, &orig, sizeof temp);
+ temp.c_lflag &= ~ECHO;
+
+ tcsetattr (fileno (infp), TCSAFLUSH, &temp);
+ tcset = 1;
}
+
+ size_t n = 0;
+ ssize_t len;
+ len = getline (&ret, &n, infp);
+ if (len == -1) {
+ perror ("getline");
+ ret = NULL;
+ goto error;
+ }
+
+ /* Remove the terminating \n if there is one. */
+ if (len > 0 && ret[len-1] == '\n')
+ ret[len-1] = '\0';
+
+ error:
+ /* Restore echo, close file descriptor. */
+ if (tty && tcset) {
+ printf ("\n");
+ tcsetattr (fileno (infp), TCSAFLUSH, &orig);
+ }
+
+ if (infp != stdin)
+ fclose (infp); /* outfp == infp, so this is closed also */
+
+ return ret;
}