>From 47b2a0ea3ca97cc4bfa9126bf853a8c54953fcbd Mon Sep 17 00:00:00 2001 From: Heikki Orsila Date: Mon, 23 Jul 2018 21:17:59 +0100 Subject: [PATCH] Add -i option for iconv to convert files in-place. Normally converted files are written to stdout. With -i option the files are replaced with converted content. --- man/iconv.1 | 6 ++++- src/iconv.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/man/iconv.1 b/man/iconv.1 index 9c6af5a..a8458f9 100644 --- a/man/iconv.1 +++ b/man/iconv.1 @@ -14,7 +14,7 @@ iconv \- character set conversion .SH SYNOPSIS .nf -iconv [\fIOPTION\fP...] [\fB\-f\fP \fIencoding\fP] [\fB\-t\fP \fIencoding\fP] [\fIinputfile\fP ...] +iconv [\fIOPTION\fP...] [\fB\-f\fP \fIencoding\fP] [\fB\-t\fP \fIencoding\fP] [\fB\-i\fP] [\fIinputfile\fP ...] iconv \fB\-l\fP .fi .SH DESCRIPTION @@ -36,6 +36,10 @@ Specifies the encoding of the input. .TP \fB\-t\fP \fIencoding\fP, \fB\-\-to\-code=\fP\fIencoding\fP Specifies the encoding of the output. +.TP +\fB\-i\fP +When this option is given, files are converted in-place instead of writing +to stdout. .PP Options controlling conversion problems: .TP diff --git a/src/iconv.c b/src/iconv.c index 505521b..b1a908a 100644 --- a/src/iconv.c +++ b/src/iconv.c @@ -28,6 +28,10 @@ #include #include #include +#include +#include +#include +#include /* Ensure that iconv_no_i18n does not depend on libintl. */ #ifdef NO_I18N @@ -595,7 +599,8 @@ static iconv_t subst_mb_to_mb_cd; /* Buffer of size ilseq_byte_subst_size*4. */ static char* subst_mb_to_mb_temp_buffer; -static void subst_mb_to_mb_fallback (const char* inbuf, size_t inbufsize) +static void subst_mb_to_mb_fallback (const char* inbuf, size_t inbufsize, + FILE* outfile) { for (; inbufsize > 0; inbuf++, inbufsize--) { const char* inptr; @@ -619,7 +624,7 @@ static void subst_mb_to_mb_fallback (const char* inbuf, size_t inbufsize) _("cannot convert byte substitution to target encoding: %s"), ilseq_byte_subst_buffer); fwrite(subst_mb_to_mb_temp_buffer,1,ilseq_byte_subst_size*4-outbytesleft, - stdout); + outfile); } } @@ -668,7 +673,8 @@ static void conversion_error_other (int errnum, const char* infilename) /* Convert the input given in infile. */ -static int convert (iconv_t cd, int infile, const char* infilename) +static int convert (iconv_t cd, int infile, const char* infilename, + FILE* outfile) { char inbuf[4096+4096]; size_t inbufrest = 0; @@ -687,7 +693,7 @@ static int convert (iconv_t cd, int infile, const char* infilename) size_t inbufsize; /* Transfer the accumulated output to its destination, in case the safe_read() call will block. */ - fflush(stdout); + fflush(outfile); inbufsize = safe_read(infile,inbuf+4096,4096); if (inbufsize == 0 || inbufsize == SAFE_READ_ERROR) { infile_error = (inbufsize == SAFE_READ_ERROR ? errno : 0); @@ -695,7 +701,7 @@ static int convert (iconv_t cd, int infile, const char* infilename) break; else { if (ilseq_byte_subst != NULL) - subst_mb_to_mb_fallback(inbuf+4096-inbufrest, inbufrest); + subst_mb_to_mb_fallback(inbuf+4096-inbufrest, inbufrest, outfile); if (!silent) conversion_error_EINVAL(infilename); status = 1; @@ -711,7 +717,7 @@ static int convert (iconv_t cd, int infile, const char* infilename) size_t res = iconv(cd,(ICONV_CONST char**)&inptr,&insize,&outptr,&outsize); if (outptr != outbuf) { int saved_errno = errno; - if (fwrite(outbuf,1,outptr-outbuf,stdout) < outptr-outbuf) { + if (fwrite(outbuf,1,outptr-outbuf,outfile) < outptr-outbuf) { status = 1; goto done; } @@ -773,7 +779,7 @@ static int convert (iconv_t cd, int infile, const char* infilename) size_t res = iconv(cd,NULL,NULL,&outptr,&outsize); if (outptr != outbuf) { int saved_errno = errno; - if (fwrite(outbuf,1,outptr-outbuf,stdout) < outptr-outbuf) { + if (fwrite(outbuf,1,outptr-outbuf,outfile) < outptr-outbuf) { status = 1; goto done; } @@ -817,7 +823,7 @@ static int convert (iconv_t cd, int infile, const char* infilename) break; } if (infile_error) { - fflush(stdout); + fflush(outfile); if (column > 0) putc('\n',stderr); error(0,infile_error, @@ -834,12 +840,39 @@ static int convert (iconv_t cd, int infile, const char* infilename) return status; } +/* create_outfile creates a temporary for the inplace mode. */ +FILE* create_outfile(char** outfilename, FILE* infile, const char* infilename) +{ + struct stat st; + char* dnamebuf; + int ret; + /* test that file is regular. */ + if (fstat(fileno(infile), &st)) + return NULL; + if (!S_ISREG(st.st_mode)) { + error(0, 0, _("will not do inplace replacement for non-regular files."), + infilename); + return NULL; + } + dnamebuf = strdup(infilename); + if (dnamebuf == NULL) + return NULL; + ret = asprintf(outfilename, "%s/.iconv.tmp.XXXXXX", dirname(dnamebuf)); + free(dnamebuf); + if (ret < 0) + return NULL; + if (mkstemp(*outfilename) < 0) + return NULL; + return fopen(*outfilename, "wb"); +} + /* ========================================================================= */ int main (int argc, char* argv[]) { const char* fromcode = NULL; const char* tocode = NULL; + int do_inplace = 0; int do_list = 0; iconv_t cd; struct iconv_fallbacks fallbacks; @@ -884,6 +917,11 @@ int main (int argc, char* argv[]) } continue; } + if (!strcmp(argv[i],"-i") || !strcmp(argv[i],"--inplace")) { + do_inplace = 1; + i++; + continue; + } if (!strcmp(argv[i],"-t") /* --t ... --to-code */ || (len >= 3 && len <= 9 && !strncmp(argv[i],"--to-code",len)) @@ -1086,7 +1124,7 @@ int main (int argc, char* argv[]) if (i == argc) status = convert(cd,fileno(stdin), /* TRANSLATORS: A filename substitute denoting standard input. */ - _("(stdin)")); + _("(stdin)"),stdout); else { status = 0; for (; i < argc; i++) { @@ -1102,8 +1140,34 @@ int main (int argc, char* argv[]) infilename); status = 1; } else { - status |= convert(cd,fileno(infile),infilename); - fclose(infile); + if (do_inplace) { + FILE* outfile = stdout; + char* outfilename = NULL; + outfile = create_outfile(&outfilename, infile, infilename); + if (outfile != NULL) { + status |= convert(cd,fileno(infile), infilename, outfile); + } else { + error(0, 0, _("%s"), infilename); + status |= 1; + } + fclose(infile); + if (outfile != NULL) { + fclose(outfile); + if (rename(outfilename, infilename)) { + /* If this fails, what can we do? */ + if (remove(outfilename)) { + error(0, 0, _("cannot remove file %s. Please clean it."), + outfilename); + } + error(0, 0, _("cannot replace (rename) file %s."), + infilename); + status |= 1; + } + } + } else { + status |= convert(cd, fileno(infile), infilename, stdout); + fclose(infile); + } } } } -- 2.11.0