Implement a dry run for -U updates before opening the programmer

This commit checks -U update requests for
  - Typos in memory names
  - Whether the files can be written or read
  - Automatic format detection if necessary

before opening the programmer. This to reduce the chances of the
programming failing midway through.

Minor additional changes:
  - Give strerror() system info when files are not read/writeable
  - Lift the auto detection message from MSG_INFO to MSG_NOTICE
  - Provide fileio_fmt_autodetect() in the AVRDUDE library
  - Rename fmtstr() in the AVRDUDE library to fileio_fmtstr() to
    avoid name clashes when an application links with it

Example:

$ avrdude -U - -U typo:r:.:h -U eeprom:w:testin:r -p ... -c ...
avrdude: can't auto detect file format for stdin/out, specify explicitly
avrdude: unknown memory type typo
avrdude: file . is not writeable (not a regular or character file?)
avrdude: file testin is not readable. No such file or directory
This commit is contained in:
Stefan Rueger 2022-08-05 17:38:59 +01:00
parent 5f5002eeaa
commit b24a1cf667
No known key found for this signature in database
GPG Key ID: B0B4F1FD86B1EC55
4 changed files with 175 additions and 22 deletions

View File

@ -100,11 +100,8 @@ static int fileio_num(struct fioparms * fio,
char * filename, FILE * f, AVRMEM * mem, int size, char * filename, FILE * f, AVRMEM * mem, int size,
FILEFMT fmt); FILEFMT fmt);
static int fmt_autodetect(char * fname);
char * fileio_fmtstr(FILEFMT format)
char * fmtstr(FILEFMT format)
{ {
switch (format) { switch (format) {
case FMT_AUTO : return "auto-detect"; break; case FMT_AUTO : return "auto-detect"; break;
@ -1402,7 +1399,7 @@ int fileio_setparms(int op, struct fioparms * fp,
static int fmt_autodetect(char * fname) int fileio_fmt_autodetect(const char * fname)
{ {
FILE * f; FILE * f;
unsigned char buf[MAX_LINE_LEN]; unsigned char buf[MAX_LINE_LEN];
@ -1547,7 +1544,7 @@ int fileio(int oprwv, char * filename, FILEFMT format,
return -1; return -1;
} }
format_detect = fmt_autodetect(fname); format_detect = fileio_fmt_autodetect(fname);
if (format_detect < 0) { if (format_detect < 0) {
avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n", avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n",
progname, fname); progname, fname);
@ -1556,8 +1553,8 @@ int fileio(int oprwv, char * filename, FILEFMT format,
format = format_detect; format = format_detect;
if (quell_progress < 2) { if (quell_progress < 2) {
avrdude_message(MSG_INFO, "%s: %s file %s auto detected as %s\n", avrdude_message(MSG_NOTICE, "%s: %s file %s auto detected as %s\n",
progname, fio.iodesc, fname, fmtstr(format)); progname, fio.iodesc, fname, fileio_fmtstr(format));
} }
} }

View File

@ -872,7 +872,9 @@ enum {
extern "C" { extern "C" {
#endif #endif
char * fmtstr(FILEFMT format); char * fileio_fmtstr(FILEFMT format);
int fileio_fmt_autodetect(const char * fname);
int fileio(int oprwv, char * filename, FILEFMT format, int fileio(int oprwv, char * filename, FILEFMT format,
struct avrpart * p, char * memtype, int size); struct avrpart * p, char * memtype, int size);
@ -936,6 +938,14 @@ const char *update_inname(const char *fn);
const char *update_outname(const char *fn); const char *update_outname(const char *fn);
const char *update_interval(int a, int b); const char *update_interval(int a, int b);
// Helper functions for dry run to determine file access
int update_is_okfile(const char *fn);
int update_is_writeable(const char *fn);
int update_is_readable(const char *fn);
int update_dryrun(struct avrpart *p, UPDATE *upd);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -901,11 +901,13 @@ int main(int argc, char * argv [])
} }
/* /*
* Now that we know which part we are going to program, locate any * Now that we know which part we are going to program, locate any -U
* -U options using the default memory region, and fill in the * options using the default memory region, fill in the device-dependent
* device-dependent default region name, either "application" (for * default region name ("application" for Xmega parts or "flash" otherwise)
* Xmega devices), or "flash" (everything else). * and check for basic problems with memory names or file access with a
* view to exit before programming.
*/ */
int doexit = 0;
for (ln=lfirst(updates); ln; ln=lnext(ln)) { for (ln=lfirst(updates); ln; ln=lnext(ln)) {
upd = ldata(ln); upd = ldata(ln);
if (upd->memtype == NULL) { if (upd->memtype == NULL) {
@ -920,12 +922,12 @@ int main(int argc, char * argv [])
} }
} }
if (!avr_mem_might_be_known(upd->memtype)) { rc = update_dryrun(p, upd);
avrdude_message(MSG_INFO, "%s: unknown memory type %s\n", progname, upd->memtype); if (rc && rc != LIBAVRDUDE_SOFTFAIL)
doexit = 1;
}
if(doexit)
exit(1); exit(1);
}
// TODO: check whether filename other than "-" is readable/writable
}
/* /*
* open the programmer * open the programmer

View File

@ -24,6 +24,9 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ac_cfg.h" #include "ac_cfg.h"
#include "avrdude.h" #include "avrdude.h"
@ -319,6 +322,147 @@ const char *update_interval(int a, int b) {
} }
// Helper functions for dry run to determine file access
int update_is_okfile(const char *fn) {
struct stat info;
// File exists and is a regular file or a character file, eg, /dev/urandom
return fn && *fn && stat(fn, &info) == 0 && !!(info.st_mode & (S_IFREG | S_IFCHR));
}
int update_is_writeable(const char *fn) {
if(!fn || !*fn)
return 0;
// Assume writing to stdout will be OK
if(!strcmp(fn, "-"))
return 1;
// File exists? If so return whether it's readable and an OK file type
if(access(fn, F_OK) == 0)
return access(fn, W_OK) == 0 && update_is_okfile(fn);
// File does not exist: try to create it
FILE *test = fopen(fn, "w");
if(test) {
unlink(fn);
fclose(test);
}
return !!test;
}
int update_is_readable(const char *fn) {
if(!fn || !*fn)
return 0;
// Assume reading from stdin will be OK
if(!strcmp(fn, "-"))
return 1;
// File exists, is readable by the process and an OK file type?
return access(fn, R_OK) == 0 && update_is_okfile(fn);
}
static void ioerror(const char *iotype, UPDATE *upd) {
avrdude_message(MSG_INFO, "%s: file %s is not %s",
progname, update_outname(upd->filename), iotype);
if(errno) {
char buf[1024];
strerror_r(errno, buf, sizeof buf);
avrdude_message(MSG_INFO, ". %s", buf);
} else if(upd->filename && *upd->filename)
avrdude_message(MSG_INFO, " (not a regular or character file?)");
avrdude_message(MSG_INFO, "\n");
}
// Basic checks to reveal serious failure before programming
int update_dryrun(struct avrpart *p, UPDATE *upd) {
static char **wrote;
static int nfwritten;
int known, format_detect, ret = LIBAVRDUDE_SUCCESS;
/*
* Reject an update if memory name is not known amongst any part (suspect a typo)
* but accept when the specific part does not have it (allow unifying i/faces)
*/
if(!avr_mem_might_be_known(upd->memtype)) {
avrdude_message(MSG_INFO, "%s: unknown memory type %s\n", progname, upd->memtype);
ret = LIBAVRDUDE_GENERAL_FAILURE;
} else if(p && !avr_locate_mem(p, upd->memtype))
ret = LIBAVRDUDE_SOFTFAIL;
known = 0;
// Necessary to check whether the file is readable?
if(upd->op == DEVICE_VERIFY || upd->op == DEVICE_WRITE || upd->format == FMT_AUTO) {
if(upd->format != FMT_IMM) {
// Need to read the file: was it written before, so will be known?
for(int i = 0; i < nfwritten; i++)
if(!wrote || (upd->filename && !strcmp(wrote[i], upd->filename)))
known = 1;
errno = 0;
if(!known && !update_is_readable(upd->filename)) {
ioerror("readable", upd);
ret = LIBAVRDUDE_GENERAL_FAILURE;
known = 1; // Pretend we know it, so no auto detect needed
}
}
}
if(!known && upd->format == FMT_AUTO) {
if(!strcmp(upd->filename, "-")) {
avrdude_message(MSG_INFO, "%s: can't auto detect file format for stdin/out, "
"specify explicitly\n", progname);
ret = LIBAVRDUDE_GENERAL_FAILURE;
} else if((format_detect = fileio_fmt_autodetect(upd->filename)) < 0) {
avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n",
progname, upd->filename);
ret = LIBAVRDUDE_GENERAL_FAILURE;
} else {
// Set format now, no need to repeat auto detection later
upd->format = format_detect;
if(quell_progress < 2)
avrdude_message(MSG_NOTICE, "%s: %s file %s auto detected as %s\n",
progname, upd->op == DEVICE_READ? "output": "input", upd->filename,
fileio_fmtstr(upd->format));
}
}
switch(upd->op) {
case DEVICE_READ:
if(upd->format == FMT_IMM) {
avrdude_message(MSG_INFO,
"%s: invalid file format 'immediate' for output\n", progname);
ret = LIBAVRDUDE_GENERAL_FAILURE;
} else {
errno = 0;
if(!update_is_writeable(upd->filename)) {
ioerror("writeable", upd);
ret = LIBAVRDUDE_GENERAL_FAILURE;
} else if(upd->filename) { // Record filename (other than stdout) is available for future reads
if(strcmp(upd->filename, "-") && (wrote = realloc(wrote, sizeof(*wrote) * (nfwritten+1))))
wrote[nfwritten++] = upd->filename;
}
}
break;
case DEVICE_VERIFY: // Already checked that file is readable
case DEVICE_WRITE:
break;
default:
avrdude_message(MSG_INFO, "%s: invalid update operation (%d) requested\n",
progname, upd->op);
ret = LIBAVRDUDE_GENERAL_FAILURE;
}
return ret;
}
int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags flags) int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags flags)
{ {
struct avrpart * v; struct avrpart * v;
@ -346,7 +490,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
// Read out the specified device memory and write it to a file // Read out the specified device memory and write it to a file
if (upd->format == FMT_IMM) { if (upd->format == FMT_IMM) {
avrdude_message(MSG_INFO, avrdude_message(MSG_INFO,
"%s: Invalid file format 'immediate' for output\n", progname); "%s: invalid file format 'immediate' for output\n", progname);
return LIBAVRDUDE_GENERAL_FAILURE; return LIBAVRDUDE_GENERAL_FAILURE;
} }
if (quell_progress < 2) if (quell_progress < 2)
@ -383,15 +527,15 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
// Write the selected device memory using data from a file // Write the selected device memory using data from a file
rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1); rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1);
if (quell_progress < 2)
avrdude_message(MSG_INFO, "%s: reading input file %s for %s%s\n",
progname, update_inname(upd->filename), mem->desc, alias_mem_desc);
if (rc < 0) { if (rc < 0) {
avrdude_message(MSG_INFO, "%s: read from file %s failed\n", avrdude_message(MSG_INFO, "%s: read from file %s failed\n",
progname, update_inname(upd->filename)); progname, update_inname(upd->filename));
return LIBAVRDUDE_GENERAL_FAILURE; return LIBAVRDUDE_GENERAL_FAILURE;
} }
size = rc; size = rc;
if (quell_progress < 2)
avrdude_message(MSG_INFO, "%s: reading input file %s for %s%s\n",
progname, update_inname(upd->filename), mem->desc, alias_mem_desc);
if(memstats(p, upd->memtype, size, &fs) < 0) if(memstats(p, upd->memtype, size, &fs) < 0)
return LIBAVRDUDE_GENERAL_FAILURE; return LIBAVRDUDE_GENERAL_FAILURE;