From owner-freebsd-sysinstall@FreeBSD.ORG Thu Feb 26 20:00:19 2015 Return-Path: Delivered-To: freebsd-sysinstall@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 68474324; Thu, 26 Feb 2015 20:00:19 +0000 (UTC) Received: from na01-bn1-obe.outbound.protection.outlook.com (mail-bn1bon0065.outbound.protection.outlook.com [157.56.111.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (Client CN "mail.protection.outlook.com", Issuer "MSIT Machine Auth CA 2" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id BE96BCCF; Thu, 26 Feb 2015 20:00:18 +0000 (UTC) Received: from [172.17.3.251] (63.252.212.99) by BLUPR0801MB660.namprd08.prod.outlook.com (10.141.254.26) with Microsoft SMTP Server (TLS) id 15.1.93.16; Thu, 26 Feb 2015 20:00:09 +0000 Message-ID: <54EF7B40.2030706@panasas.com> Date: Thu, 26 Feb 2015 15:00:00 -0500 From: "Ellis H. Wilson III" User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.4.0 MIME-Version: 1.0 To: Subject: RFC: Patches to achieve bsdinstall script without dialogs Content-Type: multipart/mixed; boundary="------------040805020502070509020205" X-Originating-IP: [63.252.212.99] X-ClientProxiedBy: DM2PR09CA0029.namprd09.prod.outlook.com (25.160.127.39) To BLUPR0801MB660.namprd08.prod.outlook.com (10.141.254.26) Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=ellisw@panasas.com; X-Microsoft-Antispam: UriScan:; X-Microsoft-Antispam: BCL:0;PCL:0;RULEID:;SRVR:BLUPR0801MB660; X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(601004)(5005006); SRVR:BLUPR0801MB660; X-Forefront-PRVS: 0499DAF22A X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10009020)(6049001)(6009001)(199003)(189002)(110136001)(86362001)(229853001)(59896002)(83506001)(64126003)(4810100001)(106356001)(512874002)(105586002)(84326002)(87976001)(64706001)(66066001)(65956001)(65806001)(2351001)(42186005)(122386002)(568964001)(36756003)(77096005)(68736005)(46102003)(33656002)(62966003)(77156002)(450100001)(4610100001)(87266999)(54356999)(101416001)(80316001)(19580395003)(40100003)(65816999)(5890100001)(92566002)(50986999)(97736003); DIR:OUT; SFP:1101; SCL:1; SRVR:BLUPR0801MB660; H:[172.17.3.251]; FPR:; SPF:None; PTR:InfoNoRecords; A:1; MX:1; LANG:en; Received-SPF: None (protection.outlook.com: panasas.com does not designate permitted sender hosts) X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:;SRVR:BLUPR0801MB660; X-OriginatorOrg: panasas.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 26 Feb 2015 20:00:09.0873 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: BLUPR0801MB660 X-BeenThere: freebsd-sysinstall@freebsd.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: Sysinstall Work List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 26 Feb 2015 20:00:19 -0000 --------------040805020502070509020205 Content-Type: text/plain; charset="utf-8"; format=flowed Content-Transfer-Encoding: 7bit All, We use FreeBSD extensively here at Panasas, and in our day-to-day software development it behooves us to have an automatic way to spin up our various clients with fresh installs. Occasionally, one of these automated installs, which relies on the various components of bsdinstall, bails, locks up, or otherwise malfunctions. At that juncture, checking our console logs is our best/only recourse since these are head-less machines, but the existing code presumes (even in the case of scripted installs) dialog will render properly. This is typically not the case in our live console or console logs for a variety of reasons, and this results in our engineers wading through a lot of dialog "curses-gunk" to find the relevant pieces of ascii that have made their way through. Therefore, after positive discussion with Nathan Whitehorn about the general utility of having an ascii-only install option, we have implemented such and been running the attached patches for a few months with success here. These enable a somewhat friendlier ASCII output when scripted installation is requested, without sacrificing (most of) the niceties of progress indication and the like. These also shouldn't impact non-scripted installation, which still utilize dialogs. With the ultimate hope of these being accepted upstream and benefiting others, I am first presenting them here for your comment. These impact the following files, and are named accordingly: usr.sbin/bsdconfig/share/common.subr usr.sbin/bsdinstall/distextract/distextract.c usr.sbin/bsdinstall/scripts/checksum usr.sbin/bsdinstall/scripts/script usr.sbin/bsdinstall/partedit/partedit.c usr.sbin/bsdinstall/partedit/scripted.c usr.sbin/bsdinstall/partedit/partedit.h These are patched from 10.1 releng, as that is what we are running. If it will smooth the process towards upstreaming things, I can patch against a different set of sources (i.e., CURRENT), just let me know. Best, ellis --------------040805020502070509020205 Content-Type: text/x-patch; name="checksum.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="checksum.patch" --- 10.1/usr.sbin/bsdinstall/scripts/checksum 2015-02-26 14:04:59.272966902 -0500 +++ usr.sbin/bsdinstall/scripts/checksum 2014-12-18 08:29:54.677947006 -0500 @@ -28,6 +28,10 @@ test -f $BSDINSTALL_DISTDIR/MANIFEST || exit 0 +if [ "$BSDINSTALL_SCRIPTED" -eq 1 ]; then + printf "$0: Verifying checksums of selected distributions:\n" +fi + percentage=0 for dist in $DISTRIBUTIONS; do distname=$(basename $dist .txz) @@ -37,9 +41,14 @@ for i in $DISTRIBUTIONS; do items="$items $i `eval echo \\\${status_$(basename $i .txz):-Pending}`" done - dialog --backtitle "FreeBSD Installer" --title "Checksum Verification" \ - --mixedgauge "Verifying checksums of selected distributions." \ - 0 0 $percentage $items + + if [ -z "$BSDINSTALL_SCRIPTED" || "$BSDINSTALL_SCRIPTED" -ne 1 ]; then + dialog --backtitle "FreeBSD Installer" --title "Checksum Verification" \ + --mixedgauge "Verifying checksums of selected distributions." \ + 0 0 $percentage $items + else + printf "$0:\tVerifying $distname...\n" + fi CK=`sha256 -q $BSDINSTALL_DISTDIR/$dist` awk -v checksum=$CK -v dist=$dist -v found=0 '{ @@ -62,8 +71,12 @@ percentage=$(echo $percentage + 100/`echo $DISTRIBUTIONS | wc -w` | bc) else eval "status_$distname=1" - dialog --backtitle "FreeBSD Installer" --title "Error" \ - --msgbox "The checksum for $dist does not match. It may have become corrupted, and should be redownloaded." 0 0 + if [ -z "$BSDINSTALL_SCRIPTED" || "$BSDINSTALL_SCRIPTED" -ne 1 ]; then + dialog --backtitle "FreeBSD Installer" --title "Error" \ + --msgbox "The checksum for $dist does not match. It may have become corrupted, and should be redownloaded." 0 0 + else + printf "$0: Error: The checksum for $dist does not match. It may have become corrupted, and should be redownloaded.\n" + fi exit 1 fi done --------------040805020502070509020205 Content-Type: text/x-patch; name="common.subr.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="common.subr.patch" --- 10.1/usr.sbin/bsdconfig/share/common.subr 2015-02-26 14:05:08.708986060 -0500 +++ usr.sbin/bsdconfig/share/common.subr 2014-12-02 11:36:33.507794626 -0500 @@ -129,11 +129,12 @@ { [ "$debug" ] || return $SUCCESS local fmt="$1"; shift + local iso8601=$(date -u +"%Y-%m-%dT%H:%M:%SZ"); case "$debugFile" in ""|+*) - printf "DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1} + printf "$iso8601: DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1} esac [ "${debugFile#+}" ] && - printf "DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}" + printf "$iso8601: DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}" return $SUCCESS } --------------040805020502070509020205 Content-Type: text/x-patch; name="distextract.c.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="distextract.c.patch" --- 10.1/usr.sbin/bsdinstall/distextract/distextract.c 2015-02-26 14:04:59.973016930 -0500 +++ usr.sbin/bsdinstall/distextract/distextract.c 2014-12-09 15:13:24.437675397 -0500 @@ -32,8 +32,13 @@ #include #include #include +#include +#include static int extract_files(int nfiles, const char **files); +static void scripted_printf(const char *fmt, ...); + +static int do_scripted = 0; int main(void) @@ -63,22 +68,31 @@ for (i = 0; i < ndists; i++) dists[i] = strsep(&diststring, " \t"); - init_dialog(stdin, stdout); - dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer"); - dlg_put_backtitle(); + if (getenv("BSDINSTALL_SCRIPTED") && strcmp(getenv("BSDINSTALL_SCRIPTED"), "1") == 0) + do_scripted = 1; + + if (!do_scripted) { + init_dialog(stdin, stdout); + dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer"); + dlg_put_backtitle(); + } if (chdir(getenv("BSDINSTALL_CHROOT")) != 0) { char error[512]; sprintf(error, "Could could change to directory %s: %s\n", getenv("BSDINSTALL_DISTDIR"), strerror(errno)); - dialog_msgbox("Error", error, 0, 0, TRUE); - end_dialog(); + if (!do_scripted) { + dialog_msgbox("Error", error, 0, 0, TRUE); + end_dialog(); + } else + scripted_printf("Error: %s\n", error); return (1); } retval = extract_files(ndists, dists); - end_dialog(); + if (!do_scripted) + end_dialog(); free(diststring); free(dists); @@ -133,7 +147,10 @@ snprintf(errormsg, sizeof(errormsg), "Error while extracting %s: %s\n", file, archive_error_string(archive)); - dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); + if (!do_scripted) + dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); + else + scripted_printf("Extract Error: %s\n", errormsg); return (-1); } @@ -156,7 +173,7 @@ struct archive_entry *entry; char errormsg[512]; char status[8]; - int i, err, progress, last_progress; + int i, err, progress, last_progress, last_progress_step; err = 0; progress = 0; @@ -171,8 +188,11 @@ items[i*2 + 1] = "Pending"; } - dialog_msgbox("", - "Checking distribution archives.\nPlease wait...", 0, 0, FALSE); + if (!do_scripted) + dialog_msgbox("", + "Checking distribution archives.\nPlease wait...", 0, 0, FALSE); + else + scripted_printf("Checking distribution archives. Please wait...\n"); /* Count all the files */ total_files = 0; @@ -184,6 +204,10 @@ } current_files = 0; + last_progress_step = 0; + + if (do_scripted) + scripted_printf("Extracting distribution files (0%%)"); for (i = 0; i < nfiles; i++) { archive = archive_read_new(); @@ -204,11 +228,22 @@ (archive_file*100)/archive_files[i]); items[i*2 + 1] = status; - if (progress > last_progress) - dialog_mixedgauge("Archive Extraction", - "Extracting distribution files...", 0, 0, - progress, nfiles, - __DECONST(char **, items)); + if (progress > last_progress) { + if (!do_scripted) + dialog_mixedgauge("Archive Extraction", + "Extracting distribution files...", 0, 0, + progress, nfiles, + __DECONST(char **, items)); + else + if (progress >= last_progress_step + 10) { + printf("\n"); + scripted_printf("Extracting distribution files (%d%%)", progress); + last_progress_step = progress; + } else { + printf("."); + fflush(stdout); + } + } err = archive_read_extract(archive, entry, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER | @@ -229,13 +264,41 @@ "Error while extracting %s: %s\n", items[i*2], archive_error_string(archive)); items[i*2 + 1] = "Failed"; - dialog_msgbox("Extract Error", errormsg, 0, 0, - TRUE); + if (!do_scripted) + dialog_msgbox("Extract Error", errormsg, 0, 0, + TRUE); + else + scripted_printf("Extract Error: %s\n", errormsg); return (err); } archive_read_free(archive); } + if (do_scripted) { + printf("\n"); + scripted_printf("Extracting distribution files (100%%)\n"); + } + + return (0); } + +static void +scripted_printf(const char *fmt, ...) +{ + static const char fmtstr[] = "%Y-%m-%dT%H:%M:%SZ"; + char timestr[32]; + time_t tmval; + struct tm tm; + va_list ap; + + time(&tmval); + gmtime_r(&tmval, &tm); + strftime(timestr, sizeof(timestr), fmtstr, &tm); + printf("%s: ", timestr); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} --------------040805020502070509020205 Content-Type: text/x-patch; name="partedit.c.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="partedit.c.patch" --- 10.1/usr.sbin/bsdinstall/partedit/partedit.c 2015-02-26 14:04:59.707606394 -0500 +++ usr.sbin/bsdinstall/partedit/partedit.c 2014-12-09 15:13:24.483607576 -0500 @@ -42,6 +42,7 @@ struct pmetadata_head part_metadata; static int sade_mode = 0; +static int scriptedpart_mode = 0; static int apply_changes(struct gmesh *mesh); static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems); @@ -61,7 +62,8 @@ gpart_revert_all(&mesh); geom_deletetree(&mesh); - end_dialog(); + if (!scriptedpart_mode) + end_dialog(); exit(1); } @@ -75,40 +77,49 @@ struct gmesh mesh; int i, op, nitems, nscroll; int error; + int autopart_mode = 0; if (strcmp(basename(argv[0]), "sade") == 0) sade_mode = 1; + if (strcmp(basename(argv[0]), "autopart") == 0) + autopart_mode = 1; + + if (strcmp(basename(argv[0]), "scriptedpart") == 0) + scriptedpart_mode = 1; + TAILQ_INIT(&part_metadata); init_fstab_metadata(); - init_dialog(stdin, stdout); - if (!sade_mode) - dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer"); - dialog_vars.item_help = TRUE; - nscroll = i = 0; - /* Revert changes on SIGINT */ signal(SIGINT, sigint_handler); - if (strcmp(basename(argv[0]), "autopart") == 0) { /* Guided */ + if (autopart_mode) /* Guided */ prompt = "Please review the disk setup. When complete, press " "the Finish button."; - part_wizard(); - } else if (strcmp(basename(argv[0]), "scriptedpart") == 0) { - error = scripted_editor(argc, argv); + else if (scriptedpart_mode) prompt = NULL; - if (error != 0) { - end_dialog(); - return (error); - } - } else { + else prompt = "Create partitions for FreeBSD. No changes will be " "made until you select Finish."; + + if (scriptedpart_mode) { + error = scripted_editor(argc, argv); + if (error != 0) + return (error); + } else { + init_dialog(stdin, stdout); + if (!sade_mode) + dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer"); + dialog_vars.item_help = TRUE; } + if (autopart_mode) + part_wizard(); + /* Show the part editor either immediately, or to confirm wizard */ + nscroll = i = 0; while (prompt != NULL) { dlg_clear(); dlg_put_backtitle(); @@ -208,7 +219,8 @@ geom_deletetree(&mesh); free(items); - end_dialog(); + if (!scriptedpart_mode) + end_dialog(); return (error); } @@ -271,9 +283,13 @@ } if (root == NULL) { - dialog_msgbox("Error", "No root partition was found. " - "The root FreeBSD partition must have a mountpoint of '/'.", - 0, 0, TRUE); + if (!scriptedpart_mode) + dialog_msgbox("Error", "No root partition was found. " + "The root FreeBSD partition must have a mountpoint of '/'.", + 0, 0, TRUE); + else + scripted_fprintf(stderr, "Error: No root partition was found. " + "The root FreeBSD partition must have a mountpoint of '/'.\n"); return (FALSE); } @@ -281,7 +297,7 @@ * Check for root partitions that we aren't formatting, which is * usually a mistake */ - if (root->newfs == NULL && !sade_mode) { + if (root->newfs == NULL && !sade_mode && !scriptedpart_mode) { dialog_vars.defaultno = TRUE; cancel = dialog_yesno("Warning", "The chosen root partition " "has a preexisting filesystem. If it contains an existing " @@ -329,9 +345,12 @@ } i = 0; - dialog_mixedgauge("Initializing", - "Initializing file systems. Please wait.", 0, 0, i*100/nitems, - nitems, __DECONST(char **, items)); + if (!scriptedpart_mode) + dialog_mixedgauge("Initializing", + "Initializing file systems. Please wait.", 0, 0, i*100/nitems, + nitems, __DECONST(char **, items)); + else + scripted_fprintf(stdout, "Initializing file systems:\n"); gpart_commit(mesh); items[i*2 + 1] = "3"; i++; @@ -342,9 +361,12 @@ TAILQ_FOREACH(md, &part_metadata, metadata) { if (md->newfs != NULL) { items[i*2 + 1] = "7"; /* In progress */ - dialog_mixedgauge("Initializing", - "Initializing file systems. Please wait.", 0, 0, - i*100/nitems, nitems, __DECONST(char **, items)); + if (!scriptedpart_mode) + dialog_mixedgauge("Initializing", + "Initializing file systems. Please wait.", 0, 0, + i*100/nitems, nitems, __DECONST(char **, items)); + else + scripted_fprintf(stdout, "\t%s...\n", items[i*2]); sprintf(message, "(echo %s; %s) >>%s 2>>%s", md->newfs, md->newfs, getenv("BSDINSTALL_LOG"), getenv("BSDINSTALL_LOG")); @@ -353,9 +375,12 @@ i++; } } - dialog_mixedgauge("Initializing", - "Initializing file systems. Please wait.", 0, 0, - i*100/nitems, nitems, __DECONST(char **, items)); + if (!scriptedpart_mode) + dialog_mixedgauge("Initializing", + "Initializing file systems. Please wait.", 0, 0, + i*100/nitems, nitems, __DECONST(char **, items)); + else + scripted_fprintf(stdout, "Completed initializing file systems!\n"); for (i = 1; i < nitems; i++) free(__DECONST(char *, items[i*2])); @@ -369,7 +394,10 @@ if (fstab == NULL) { sprintf(message, "Cannot open fstab file %s for writing (%s)\n", getenv("PATH_FSTAB"), strerror(errno)); - dialog_msgbox("Error", message, 0, 0, TRUE); + if (!scriptedpart_mode) + dialog_msgbox("Error", message, 0, 0, TRUE); + else + scripted_fprintf(stderr, "Error: %s\n", message); return (-1); } fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n"); --------------040805020502070509020205 Content-Type: text/x-patch; name="partedit.h.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="partedit.h.patch" --- 10.1/usr.sbin/bsdinstall/partedit/partedit.h 2015-02-26 14:04:59.712862309 -0500 +++ usr.sbin/bsdinstall/partedit/partedit.h 2014-12-09 15:13:24.515796476 -0500 @@ -32,6 +32,7 @@ #include #include #include +#include struct gprovider; struct gmesh; @@ -56,6 +57,7 @@ int part_wizard(void); int scripted_editor(int argc, const char **argv); +void scripted_fprintf(FILE *fp, const char *fmt, ...); int wizard_makeparts(struct gmesh *mesh, const char *disk, int interactive); /* gpart operations */ --------------040805020502070509020205 Content-Type: text/x-patch; name="script.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="script.patch" --- 10.1/usr.sbin/bsdinstall/scripts/script 2015-02-26 14:04:59.705037107 -0500 +++ usr.sbin/bsdinstall/scripts/script 2014-12-09 15:13:24.608203927 -0500 @@ -38,6 +38,7 @@ ############################################################ CONFIGURATION # VARIABLES: +export BSDINSTALL_SCRIPTED=1 # PARTITIONS # DISTRIBUTIONS # BSDINSTALL_DISTDIR @@ -47,7 +48,7 @@ # # Strings that should be moved to an i18n file and loaded with f_include_lang() # -msg_installation_error="Installation Error!" +msg_installation_error="Installation Error! See" ############################################################ FUNCTIONS @@ -58,9 +59,7 @@ local file f_getvar "$VAR_DEBUG_FILE#+" file if [ "$file" ]; then - f_dialog_title "$msg_installation_error" - f_dialog_textbox "$file" - # No need to restore title, pining for the fjords + printf "$0: $msg_installation_error $file" fi exit 1 @@ -74,7 +73,7 @@ SCRIPT="$1" shift -f_dprintf "Began Installation at %s" "$( date )" +f_dprintf "$0: Began Installation at %s" "$( date )" rm -rf $BSDINSTALL_TMPETC mkdir $BSDINSTALL_TMPETC @@ -89,7 +88,7 @@ export debugFile="$BSDINSTALL_LOG" f_quietly f_debug_init # NB: Being scripted, let debug go to terminal for invalid debugFile - f_dprintf "Began Instalation at %s" "$( date )" + f_dprintf "$0: Began Installation at %s" "$( date )" fi # Make partitions @@ -127,7 +126,7 @@ bsdinstall entropy bsdinstall umount -f_dprintf "Installation Completed at %s" "$( date )" +f_dprintf "$0: Installation Completed at %s" "$( date )" trap true EXIT --------------040805020502070509020205 Content-Type: text/x-patch; name="scripted.c.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="scripted.c.patch" --- 10.1/usr.sbin/bsdinstall/partedit/scripted.c 2015-02-26 14:04:59.277321560 -0500 +++ usr.sbin/bsdinstall/partedit/scripted.c 2014-12-09 15:13:24.541703483 -0500 @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include "partedit.h" @@ -76,7 +78,7 @@ error = geom_gettree(&mesh); if (provider_for_name(&mesh, disk) == NULL) { - fprintf(stderr, "GEOM provider %s not found\n", disk); + scripted_fprintf(stderr, "GEOM provider %s not found\n", disk); geom_deletetree(&mesh); return (-1); } @@ -159,7 +161,7 @@ input++; partconfig = strchr(input, '}'); if (partconfig == NULL) { - fprintf(stderr, "Malformed partition setup " + scripted_fprintf(stderr, "Malformed partition setup " "string: %s\n", input); return (1); } @@ -174,7 +176,7 @@ else if (scheme == NULL) scheme = strsep(&input, " \t\n"); else { - fprintf(stderr, "Unknown directive: %s\n", + scripted_fprintf(stderr, "Unknown directive: %s\n", strsep(&input, " \t\n")); return (1); } @@ -211,3 +213,21 @@ return (0); } +void +scripted_fprintf(FILE *fp, const char *fmt, ...) +{ + static const char fmtstr[] = "%Y-%m-%dT%H:%M:%SZ"; + char timestr[32]; + time_t tmval; + struct tm tm; + va_list ap; + + time(&tmval); + gmtime_r(&tmval, &tm); + strftime(timestr, sizeof(timestr), fmtstr, &tm); + printf("%s: ", timestr); + + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); +} --------------040805020502070509020205--