[RFC/PATCH] Implement new '--output-dir' option.

Thadeu Lima de Souza Cascardo cascardo en cascardo.info
Sab Mar 22 12:33:19 UTC 2014


On Sat, Mar 22, 2014 at 03:33:24AM -0300, Sergio Durigan Junior wrote:
> This commit implements the new '--output-dir' (or '-o') option.  It can
> be used to specify where the receipt of the submission will be saved.
> 
> Currently, rnetclient saves the receipt in your $HOME, with a generic
> name which is composed using the CPF and some random digits.  This is
> not good because the proprietary IRPF program expects the receipt name
> to be the same as the declaration name, but with the extension renamed
> from ".DEC" to ".REC".
> 
> This patch implements some new concepts.  First, if the user provides an
> output directory in the command line, we save the receipt there.  If she
> does not provide anything, then we save the receipt in the current
> working dir (CWD), which is a more sensitive decision IMO.  Also, and
> perhaps more important, is the fact that now the program automagically
> detects when the filename has the ".DEC" extension, and uses the same
> filename for the receipt in this case (replacing ".DEC" by ".REC", as
> expected).  This makes the proprietary crap recognize our receipt
> out-of-the-box, without having to worry with renamings.  It is worth
> mentioning that if the user provides a declaration file which does not
> have the ".DEC" extension, then rnetclient fallbacks to the old behavior
> and saves the filename as "$CPF.REC" (I chose not to use random digits
> in the end of the filename).
> 
> On a side note, I would like to say that I am not entirely happy with
> the way we handle missing directories and files, but that is a topic for
> a completely different patch...

NAcked. See my comments below.
Rejeitado, ver comentários abaixo.

> ---
>  rnetclient.c | 131 ++++++++++++++++++++++++++++++++++++++++-------------------
>  1 file changed, 89 insertions(+), 42 deletions(-)
> 
> diff --git a/rnetclient.c b/rnetclient.c
> index 181ac2f..f1535fe 100644
> --- a/rnetclient.c
> +++ b/rnetclient.c
> @@ -22,6 +22,8 @@
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <errno.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
>  #include <unistd.h>
>  #include <sys/socket.h>
>  #include <netinet/in.h>
> @@ -46,7 +48,7 @@ static const char rnetclient_doc[] =
>  	"Send the Brazilian Income Tax Report to the Brazilian "
>  	"Tax Authority";
>  static const char rnetclient_args_doc[] =
> -	"[-d|--declaration] FILE";
> +	"[-d|--declaration] FILE [-o|--output-dir DIRECTORY]";
>  
>  /* Description and definition of each option accepted by the program.  */
>  
> @@ -55,12 +57,19 @@ static const struct argp_option rnetclient_options_desc[] = {
>  	  "The Income Tax Report file that will be sent.",
>  	  0 },
>  
> +	{ "output-dir", 'o', "DIRECTORY", 0,
> +	  "The directory where you wish to save the receipt.",
> +	  0 },
> +
>  	{ NULL },
>  };
>  
>  struct rnetclient_args {
>  	/* File representing the declaration.  */
>  	char *input_file;
> +
> +	/* Output directory to save the receipt.  */
> +	char *output_dir;
>  };
>  
>  /* Parser for command line arguments.  */
> @@ -75,6 +84,10 @@ static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state
>  		a->input_file = arg;
>  		break;
>  
> +	case 'o':
> +		a->output_dir = arg;
> +		break;
> +
>  	case ARGP_KEY_ARG:
>  		/* The user has possibly provided a filename without
>  		   using any switches (e.g., by running './rnetclient
> @@ -331,58 +344,92 @@ static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
>  	return 0;
>  }
>  
> -static void save_rec_file(char *cpf, char *buffer, int len)
> +static void save_rec_file(char *cpf, char *buffer, int len, const struct rnetclient_args *args)
>  {
> -	int fd;
> -	char *filename;
> -	char *home, *tmpdir;
> -	mode_t mask;
> -	size_t fnlen;
> -	int r;
> -	home = getenv("HOME");
> -	if (!home) {
> -		tmpdir = getenv("TMPDIR");
> -		if (!tmpdir)
> -			tmpdir = "/tmp";
> -		home = tmpdir;
> +	FILE *f;
> +	char cwd[PATH_MAX];
> +	char *path, *fname, *tmp;
> +	size_t fname_len, r;
> +	mode_t old_mask;
> +	/* If the user provided the output directory where she wishes
> +	   to save the receipt, then we use it.  Otherwise, we save
> +	   the file in the current working directory (CWD).  */
> +	if (args->output_dir == NULL)
> +		path = getcwd(cwd, PATH_MAX);

By default, I would rather use the same directory as the input file. But
it could be an incremental patch.

> +	else {
> +		struct stat st;
> +		if (stat(args->output_dir, &st) < 0) {
> +			fprintf(stderr, "Could not stat directory \"%s\": %s\n", args->output_dir, strerror(errno));
> +			return;
> +		}
> +		if (!S_ISDIR(st.st_mode)) {
> +			fprintf(stderr, "Error: \"%s\" is a not a directory.\n", args->output_dir);
> +			return;
> +		}
> +		path = args->output_dir;
>  	}
> -	fnlen = strlen(home) + strlen(cpf) + 13;
> -	filename = malloc(fnlen);
> -	snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
> -	mask = umask(0177);
> -	fd = mkstemp(filename);

>From mkstemp manpage:
The file is opened with the open(2) O_EXCL flag, guaranteeing that the
caller is the process that creates the file.

The call to fopen below will overwrite any existing files. Please, open
it with O_EXCL. Optionally, use a program option for the user to
explictly allow overwrites, and remove the flag in that case.

This new option could be an incremental patch, but the default behavior
should not overwrite any files.

The last incremental patch that I would like to see in this series is
open the file before connecting to the server, just in case it can't be
written to, we advise the user and do not send the report.

Cascardo.

> -	if (fd < 0) {
> -		fprintf(stderr, "Could not create receipt file: %s\n",
> -						strerror(errno));
> -		goto out;
> +	/* Now it's time to decide which filename to write.  We use
> +	   the declaration's filename as a base layout, because the
> +	   proprietary version of the IRPF program only recognizes
> +	   receipts if they have the same name as the declaration
> +	   files (disconsidering the extensions).  For example, if the
> +	   declaration file is named "123.DEC", the receipt should be
> +	   named "123.REC".  Therefore, if the declaration file has
> +	   the ".DEC" extension, we strip it out and add the ".REC".
> +	   Otherwise, we use the default template, which is to save
> +	   the receipt with the name "$CPF.REC".  */
> +	tmp = strstr(args->input_file, ".DEC");
> +	if (tmp != NULL && tmp[sizeof(".DEC") - 1] == '\0') {
> +		const char *p;
> +		/* We found the ".REC" extension.  */
> +		p = strdup(args->input_file);
> +		/* Replacing the ".DEC" by ".REC".  Fortunately, we
> +		   just have to change one letter.  */
> +		tmp = strstr(p, ".DEC");
> +		tmp[1] = 'R';
> +		fname_len = strlen(p) + strlen(path) + 2;
> +		fname = alloca(fname_len);
> +		snprintf(fname, fname_len, "%s/%s", path, p);
> +	} else {
> +		/* The declaration filename does not follow the
> +		   convention, so we will not use it as a template.
> +		   We just generate a filename using "$CPF.REC".  */
> +		fname_len = strlen(cpf) + strlen(path) + sizeof(".REC") + 2;
> +		fname = alloca(fname_len);
> +		snprintf(fname, fname_len, "%s/%s.REC", path, cpf);
>  	}
> -	r = write(fd, buffer, len);
> -	if (r != len) {
> -		fprintf(stderr, "Could not write to receipt file%s%s\n",
> -			r < 0 ? ": " : ".",
> -			r < 0 ? strerror(errno) : "");
> -		goto out;
> +	/* Now, umask, open the file and write.  */
> +	old_mask = umask(0177);
> +	f = fopen(fname, "w");
> +	if (f == NULL) {
> +		fprintf(stderr, "Could not create receipt file: %s\n", strerror(errno));
> +		umask(old_mask);
> +		return;
>  	}
> -	fprintf(stderr, "Wrote the receipt to %s.\n", filename);
> -out:
> -	close(fd);
> -	free(filename);
> -	umask(mask);
> +	do {
> +		r = fwrite(buffer, sizeof(char), len, f);
> +	} while (r != len && errno == EAGAIN);
> +	if (r != len)
> +		fprintf(stderr, "Could not write to receipt file: %s", strerror(errno));
> +	else
> +		fprintf(stderr, "Wrote the receipt file to %s.\n", fname);
> +	umask(old_mask);
> +	fclose(f);
>  }
>  
> -static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
> +static void handle_response_text_and_file(char *cpf, struct rnet_message *message, const struct rnetclient_args *args)
>  {
>  	char *value;
>  	int vlen;
>  	if (!rnet_message_parse(message, "texto", &value, &vlen))
>  		fprintf(stderr, "%.*s\n", vlen, value);
>  	if (!rnet_message_parse(message, "arquivo", &value, &vlen))
> -		save_rec_file(cpf, value, vlen);
> +		save_rec_file(cpf, value, vlen, args);
>  }
>  
> -static void handle_response_already_found(char *cpf, struct rnet_message *message)
> +static void handle_response_already_found(char *cpf, struct rnet_message *message, const struct rnetclient_args *args)
>  {
> -	handle_response_text_and_file(cpf, message);
> +	handle_response_text_and_file(cpf, message, args);
>  }
>  
>  static void handle_response_error(struct rnet_message *message)
> @@ -457,19 +504,19 @@ int main(int argc, char **argv)
>  	}
>  	switch (message->buffer[0]) {
>  	case 1: /* go ahead */
> -		handle_response_text_and_file(cpf, message);
> +		handle_response_text_and_file(cpf, message, &rnet_args);
>  		break;
>  	case 3: /* error */
>  		handle_response_error(message);
>  		finish = 1;
>  		break;
>  	case 4:
> -		handle_response_already_found(cpf, message);
> +		handle_response_already_found(cpf, message, &rnet_args);
>  		finish = 1;
>  		break;
>  	case 2:
>  	case 5:
> -		handle_response_text_and_file(cpf, message);
> +		handle_response_text_and_file(cpf, message, &rnet_args);
>  		finish = 1;
>  		break;
>  	}
> @@ -495,7 +542,7 @@ int main(int argc, char **argv)
>  	case 4:
>  	case 5:
>  	case 1:
> -		handle_response_text_and_file(cpf, message);
> +		handle_response_text_and_file(cpf, message, &rnet_args);
>  		break;
>  	}
>  	
> -- 
> 1.8.1.4
> 
> 
> -- 
> Sergio


Más información sobre la lista de distribución Softwares-impostos