Local branch gitlab to github merge

This commit is contained in:
Philip Smart
2021-08-22 10:27:14 +01:00
4 changed files with 978 additions and 0 deletions

14
.gitignore vendored
View File

@@ -73,15 +73,29 @@ zputa/*.i
zputa/*.ii
zputa/*.s
zOS/main.save
<<<<<<< HEAD
/tzio/
.dobuild
SD/
=======
.dobuild
SD/
common/osd.sav.c
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2
frdm/
frdmk64f_usb_msd_host_bootloader_mcux.zip
frdmk64f_usb_msd_host_bootloader_mcux/
getopt_long.c
<<<<<<< HEAD
libraries/include/optparse.h
minicom.cap
startup/app_zos_zpu.tmpl2
=======
minicom.cap
rrrr
startup/app_zos_zpu.tmpl2
startup/app_zos_zpu.tmpl3
tzio/
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2
zOS/main.bak

503
apps/tzflupd/tzflupd.c.orig Normal file
View File

@@ -0,0 +1,503 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: tzdump.c
// Created: Jan 2021
// Author(s): Philip Smart
// Description: A TranZPUter helper utility to update the program in the K64F ARM Processor. This
// application takes a binary and flashes it into the K64F Flash Memory in order to
// update zOS/ZPUTA or flash any other program (NB. flashing any other program may
// need an external OpenSDA programmer to reprogram the K64F with zOS).
// Credits:
// Copyright: (c) 2019-2021 Philip Smart <philip.smart@net2net.org>
//
// History: Jan 2021 - Initial write of the TranZPUter software using NXP/Freescale flash
// driver source.
// Feb 2021 - Getopt too buggy with long arguments so replaced with optparse.
// Mar 2021 - Change sector size to K64F default and fixed some bugs.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// For the Freescale driver code, please see the license at the top of the driver source file.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__K64F__)
#include <stdio.h>
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include "k64f_soc.h"
#include <../../libraries/include/stdmisc.h>
#elif defined(__ZPU__)
#include <stdint.h>
#include <stdio.h>
#include "zpu_soc.h"
#include <stdlib.h>
#include <ctype.h>
#include <stdmisc.h>
#else
#error "Target CPU not defined, use __ZPU__ or __K64F__"
#endif
#include "interrupts.h"
#include "ff.h" /* Declarations of FatFs API */
//#include "utils.h"
//
#if defined __ZPUTA__
#include "zputa_app.h"
#elif defined __ZOS__
#include "zOS_app.h"
#else
#error OS not defined, use __ZPUTA__ or __ZOS__
#endif
// Getopt_long is buggy so we use optparse.
#define OPTPARSE_IMPLEMENTATION
#define OPTPARSE_API static
#include <optparse.h>
//
#include <app.h>
#if defined(__K64F__)
#include <tranzputer_m.h>
#include "tzflupd.h"
#include "fsl_flash.h"
#endif
// Utility functions.
//#include <tools.c>
// Version info.
#define VERSION "v1.2"
#define VERSION_DATE "11/03/2021"
#define APP_NAME "TZFLUPD"
// Global scope variables.
FATFS diskHandle;
char buffer[FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE];
uint8_t FLASH_PROTECTION_SIGNATURE[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xde, 0xf9, 0xff, 0xff};
// Simple help screen to remmber how this utility works!!
//
void usage(void)
{
printf("%s %s\n", APP_NAME, VERSION);
printf("\nCommands:-\n");
printf(" -h | --help This help text.\n");
printf(" -f | --file Binary file to upload and flash into K64F.\n");
printf("\nOptions:-\n");
printf(" -d | --debug Add debug steps to programming.\n");
printf(" -v | --verbose Output more messages.\n");
printf("\nExamples:\n");
printf(" tzflupd -f zOS_22012021_001.bin --verbose # Upload and program the zOS_22012021_001.bin file into the K64F flash memory.\n");
}
// Method to initialise the SD card and mount the first partition. The file we need to upload into the K64F Flash RAM is stored on this card.
//
FRESULT initSDCard(void)
{
// Locals.
//
FRESULT result = FR_NOT_ENABLED;
// Make a complete initialisation as we are using a new Fat FS instance.
//
if(!disk_initialize(0, 1))
{
sprintf(buffer, "0:");
result = f_mount(&diskHandle, buffer, 0);
}
return(result);
}
// Local memory dump routine for debug purposes.
//
int dumpMemory(uint32_t memaddr, uint32_t memsize, uint32_t memwidth, uint32_t dispaddr, uint8_t dispwidth)
{
uint8_t displayWidth = dispwidth;;
uint32_t pnt = memaddr;
uint32_t endAddr = memaddr + memsize;
uint32_t addr = dispaddr;
uint32_t i = 0;
//uint32_t data;
int8_t keyIn;
char c = 0;
// If not set, calculate output line width according to connected display width.
//
if(displayWidth == 0)
{
switch(getScreenWidth())
{
case 40:
displayWidth = 8;
break;
case 80:
displayWidth = 16;
break;
default:
displayWidth = 32;
break;
}
}
while (1)
{
printf("%08lX", addr); // print address
printf(": ");
// print hexadecimal data
for (i=0; i < displayWidth; )
{
switch(memwidth)
{
case 16:
if(pnt+i < endAddr)
printf("%04X", *(uint16_t *)(pnt+i));
else
printf(" ");
//printf(" ");
i+=2;
break;
case 32:
if(pnt+i < endAddr)
printf("%08lX", *(uint32_t *)(pnt+i));
else
printf(" ");
i+=4;
break;
case 8:
default:
if(pnt+i < endAddr)
printf("%02X", *(uint8_t *)(pnt+i));
else
printf(" ");
i++;
break;
}
fputc((char)' ', stdout);
}
// print ascii data
printf(" |");
// print single ascii char
for (i=0; i < displayWidth; i++)
{
c = (char)*(uint8_t *)(pnt+i);
if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~'))
fputc((char)c, stdout);
else
fputc((char)' ', stdout);
}
puts("|");
// Move on one row.
pnt += displayWidth;
addr += displayWidth;
// User abort (ESC), pause (Space) or all done?
//
keyIn = getKey(0);
if(keyIn == ' ')
{
do {
keyIn = getKey(0);
} while(keyIn != ' ' && keyIn != 0x1b);
}
// Escape key pressed, exit with 0 to indicate this to caller.
if (keyIn == 0x1b)
{
return(0);
}
// End of buffer, exit the loop.
if(pnt >= (memaddr + memsize))
{
break;
}
}
// Normal exit, return -1 to show no key pressed.
return(-1);
}
// Main entry and start point of a zOS/ZPUTA Application. Only 2 parameters are catered for and a 32bit return code, additional parameters can be added by changing the appcrt0.s
// startup code to add them to the stack prior to app() call.
//
// Return code for the ZPU is saved in _memreg by the C compiler, this is transferred to _memreg in zOS/ZPUTA in appcrt0.s prior to return.
// The K64F ARM processor uses the standard register passing conventions, return code is stored in R0.
//
uint32_t app(uint32_t param1, uint32_t param2)
{
// Initialisation.
//
int argc = 0;
int help_flag = 0;
int uploadFNLen = 0;
int debug_flag = 0;
int verbose_flag = 0;
int opt;
int updateFNLen = 0;
long val = 0;
char *argv[20];
char *ptr = strtok((char *)param1, " ");
char updateFile[32];
uint32_t fileSize;
uint32_t readSize;
uint32_t sizeToRead;
uint32_t bytesProcessed;
flash_config_t flashDriver; // Flash driver Structure
status_t flashResult; // Return code from each flash driver function
FRESULT fResult; // Fat FS result.
FIL fileHandle; // File Handle.
// If the invoking command is given, add it to argv at the start.
//
if(param2 != 0)
{
argv[argc++] = (char *)param2;
}
// Now convert the parameter line into argc/argv suitable for getopt to use.
while (ptr && argc < 20-1)
{
argv[argc++] = ptr;
ptr = strtok(0, " ");
}
argv[argc] = 0;
// Define parameters to be processed.
struct optparse options;
static struct optparse_long long_options[] =
{
{"help", 'h', OPTPARSE_NONE},
{"file", 'f', OPTPARSE_REQUIRED},
{"debug", 'd', OPTPARSE_NONE},
{"verbose", 'v', OPTPARSE_NONE},
{0}
};
// Parse the command line options.
//
optparse_init(&options, argv);
while((opt = optparse_long(&options, long_options, NULL)) != -1)
{
switch(opt)
{
case 'h':
help_flag = 1;
break;
case 'f':
strcpy(updateFile, options.optarg);
updateFNLen = strlen(updateFile);
break;
case 'd':
debug_flag = 1;
break;
case 'v':
verbose_flag = 1;
break;
case '?':
printf("%s: %s\n", argv[0], options.errmsg);
return(1);
}
}
// Validate the input.
if(help_flag == 1)
{
usage();
return(0);
}
if(updateFNLen == 0)
{
printf("Update file needs to be specified.\n");
return(1);
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
// As the kernel will be erased we need to assume kernel functionality so initialise kernel features, such as the SD card here as we use them locally.
//---------------------------------------------------------------------------------------------------------------------------------------------------------------
if((fResult=initSDCard()) != FR_OK)
{
printf("ERROR: Failed to re-initialise the SD card, cannot continue.\n");
return(10);
}
// Initialise the flash driver.
memset(&flashDriver, 0, sizeof(flash_config_t));
flashResult = FLASH_Init(&flashDriver);
if (kStatus_FLASH_Success != flashResult)
{
printf("Error: Failed to initialize Flash memory driver!\n");
return(11);
}
// Try and open the source file.
fResult = f_open(&fileHandle, updateFile, FA_OPEN_EXISTING | FA_READ);
// Get the size of the file.
if(fResult == FR_OK)
fResult = f_lseek(&fileHandle, f_size(&fileHandle));
if(fResult == FR_OK)
fileSize = (uint32_t)f_tell(&fileHandle);
if(fResult == FR_OK)
fResult = f_lseek(&fileHandle, 0);
// Verify that the binary is a K64F program. Do this by comparing the protection area which is static and non changing.
//
fResult = f_lseek(&fileHandle, FLASH_PROTECTION_START_ADDR);
if(fResult == FR_OK)
fResult = f_read(&fileHandle, buffer, FLASH_PROTECTION_SIZE, &readSize);
if(fResult == FR_OK)
{
for(int idx=0; idx < FLASH_PROTECTION_SIZE; idx++)
{
if(buffer[idx] != FLASH_PROTECTION_SIGNATURE[idx])
{
printf("Error: Update file doesnt look like a valid K64F program binary, aborting!\n");
return(12);
}
}
fResult = f_lseek(&fileHandle, 0);
}
// If all ok, indicate file has been opened then prepare to read and flash.
if(fResult == FR_OK)
{
// Indicate file, size and that it has been verified using the security flags.
printf("%s %s\n\n", APP_NAME, VERSION);
printf("Firmware update file: %s, size=%ld bytes\n\n", updateFile, fileSize);
printf("*******************************************************************************************************************\n");
printf("Flash will now commence, no further output will be made until the flash is successfully programmed.\n");
printf("If no further output is seen within 30 seconds, please assume the programming failed and make a hard reset.\n");
printf("If device doesnt restart use an OpenSDA or JTAG programmer to reprogram the OS.\n");
printf("*******************************************************************************************************************\n");
// Slight delay to allow the output to flush to the user serial console.
uint32_t startTime = *G->millis;
while((*G->millis - startTime) < 1000) {};
// Enter a loop, interrupts disabled, reading sector at a time from the SD card and flashing it into the Flash RAM.
//
__disable_irq();
bytesProcessed = 0;
do {
sizeToRead = (fileSize-bytesProcessed) > FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE ? FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE : fileSize - bytesProcessed;
fResult = f_read(&fileHandle, buffer, sizeToRead, &readSize);
if (fResult || readSize == 0) break; /* error or eof */
// If a sector isnt full, ie. last sector, pad with 0xFF.
//
if(readSize != FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE)
{
for(uint16_t idx=readSize; idx < FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE; idx++) { buffer[idx] = 0xFF; }
}
// Flash the sector into the correct location governed by the bytes already processed. We flash a K64F programming sector at a time with unused space set to 0xFF.
//
flashResult = FLASH_Erase(&flashDriver, bytesProcessed, FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE, kFLASH_ApiEraseKey);
// If no previous errors, program the next sector.
if(flashResult == kStatus_FLASH_Success)
{
flashResult = FLASH_Program(&flashDriver, bytesProcessed, (uint32_t*)buffer, FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE);
}
// Update the address/bytes processed count.
bytesProcessed += FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE;
} while(bytesProcessed < fileSize && flashResult == kStatus_FLASH_Success);
__enable_irq();
// Verbose output.
if(verbose_flag)
printf("Bytes processed:%ld, exit status:%s\n", bytesProcessed, flashResult == kStatus_FLASH_Success ? "Success" : "Fail");
// Success in programming, clear rest of flash RAM.
if(flashResult == kStatus_FLASH_Success)
{
// Move to the next full sector as erase operates on sector boundaries.
bytesProcessed = bytesProcessed + (bytesProcessed % FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE);
// Verbose output.
if(verbose_flag)
printf("Clearing remainder of flash:%ld bytes\n", ((uint32_t)flashDriver.PFlashTotalSize-bytesProcessed));
// Erase remainder of the device, caters for previous image being larger than update.
__disable_irq();
flashResult = FLASH_Erase(&flashDriver, bytesProcessed, ((uint32_t)flashDriver.PFlashTotalSize-bytesProcessed), kFLASH_ApiEraseKey);
__enable_irq();
}
// Any errors, report (assuming kernel still intact) and hang.
if (flashResult != kStatus_FLASH_Success)
{
// This message may not be seen if the kernel has been wiped. put here in-case of error before erase.
printf("Error: Failed to program new upgrade into Flash memory area!\n");
printf(" Reset device. If device doesnt restart use an OpenSDA or JTAG programmer to reprogram.\n\n");
while(1) {};
}
// Debug - place a message in an unused sector which can be checked to see if programming worked.
if(debug_flag)
{
sprintf(buffer, "FLASH PROGRAMMING CHECK MESSAGE");
for(uint16_t idx=strlen(buffer); idx < FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE; idx++) { buffer[idx] = 0x00; }
__disable_irq();
flashResult = FLASH_Erase(&flashDriver, bytesProcessed+FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE, FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE, kFLASH_ApiEraseKey);
flashResult = FLASH_Program(&flashDriver, bytesProcessed+FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE, (uint32_t*)buffer, FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE);
__enable_irq();
printf("Wrote check string at: %08lx\n", bytesProcessed+FSL_FEATURE_FLASH_PFLASH_BLOCK_SECTOR_SIZE);
}
// Just in case we have output connectivity. If the update doesnt change too much then we should maintain connectivity with the USB.
if(flashResult != kStatus_FLASH_Success)
{
printf("Error: Flash programming failed, addr:%ld, result:%d\n", bytesProcessed, flashResult);
}
// Tidy up for exit.
f_close(&fileHandle);
} else
{
printf("Error: Failed to read update file:%s, aborting!\n", updateFile);
return(13);
}
printf("Programming successful, please reset the device to activate update!\n");
return(0);
}
#ifdef __cplusplus
}
#endif

View File

@@ -4,7 +4,12 @@
# TARGET=MZ-700
# TARGET=MZ-80A
<<<<<<< HEAD
ZPU_SHARPMZ_BUILD=1
=======
TARGET=MZ-80A
ZPU_SHARPMZ_BUILD=0
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2
#ZPU_SHARPMZ_APPADDR=0x100000
#ZPU_SHARPMZ_APPSIZE=0x70000
#ZPU_SHARPMZ_HEAPSIZE=0x8000
@@ -59,6 +64,7 @@ if [ "${ZPU_E115_BUILD}x" != "x" -a ${ZPU_E115_BUILD} = 1 ]; then
cp -r build/SD/* SD/Dev/
fi
<<<<<<< HEAD
echo "Building for K64F"
./build.sh -C K64F -O zos -N 0x10000 -d -T
if [ $? != 0 ]; then
@@ -86,6 +92,24 @@ cp ${ROOT_DIR}/zSoft/zOS/main.bin ${ROOT_DIR}/zSoft/zOS/main.bak
if [ $? != 0 ]; then
exit 1
fi
=======
if [ "${TARGET}x" != "x" -a "${TARGET}" = "MZ-80A" ]; then
echo "Building for K64F on MZ-80A"
./build.sh -C K64F -O zos -N 0x10000 -d -T
if [ $? != 0 ]; then
echo "Error building K64F Distribution..."
exit 1
fi
else
echo "Building for K64F"
./build.sh -C K64F -O zos -N 0x18000 -d -T
if [ $? != 0 ]; then
echo "Error building K64F Distribution..."
exit 1
fi
fi
cp -r build/SD/* SD/K64F/
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2
# Ensure the TZFS target directories exist
k64fsddir=${ROOT_DIR}/zSoft/SD/K64F
@@ -118,6 +142,37 @@ if [ $? != 0 ]; then
echo "CPM disks assembly failed..."
exit 1
fi
<<<<<<< HEAD
=======
)
if [ $? != 0 ]; then
exit 1
fi
# Copy the files to the remote build server.
cd ${ROOT_DIR}/zSoft
rsync -avh * psmart@192.168.15.205:${ROOT_DIR}/zSoft/
if [ $? != 0 ]; then
echo "Error syncing K64F Distribution..."
exit 1
fi
# Simple mechanism to prevent remote build and programming of the K64F.
diff ${ROOT_DIR}/zSoft/zOS/main.bin ${ROOT_DIR}/zSoft/zOS/main.bak
if [ $? -ne 0 ]; then
cd ${ROOT_DIR}/zSoft
echo "GO" > ${ROOT_DIR}/zSoft/.dobuild
rsync -avh .dobuild psmart@192.168.15.205:${ROOT_DIR}/zSoft/
if [ $? != 0 ]; then
echo "Error syncing K64F Distribution..."
exit 1
fi
fi
cp ${ROOT_DIR}/zSoft/zOS/main.bin ${ROOT_DIR}/zSoft/zOS/main.bak
)
if [ $? != 0 ]; then
exit 1
fi
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2
# Use copytosd.sh to transfer files to an SD card. Still need to copy the k64F files manually.
# ---------------
@@ -136,7 +191,10 @@ fi
#cp $tzfsdir/MZF/${TARGET}/* $k64fsddir/MZF/
#cp $tzfsdir/BAS/* $k64fsddir/BAS/
#cp $tzfsdir/CAS/* $k64fsddir/CAS/
<<<<<<< HEAD
)
if [ $? != 0 ]; then
exit 1
fi
=======
>>>>>>> upstream/v2.1-tranZPUter-SW-HW_v2.2

View File

@@ -0,0 +1,403 @@
/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
*
* This is free and unencumbered software released into the public domain.
*
* To get the implementation, define OPTPARSE_IMPLEMENTATION.
* Optionally define OPTPARSE_API to control the API's visibility
* and/or linkage (static, __attribute__, __declspec).
*
* The POSIX getopt() option parser has three fatal flaws. These flaws
* are solved by Optparse.
*
* 1) Parser state is stored entirely in global variables, some of
* which are static and inaccessible. This means only one thread can
* use getopt(). It also means it's not possible to recursively parse
* nested sub-arguments while in the middle of argument parsing.
* Optparse fixes this by storing all state on a local struct.
*
* 2) The POSIX standard provides no way to properly reset the parser.
* This means for portable code that getopt() is only good for one
* run, over one argv with one option string. It also means subcommand
* options cannot be processed with getopt(). Most implementations
* provide a method to reset the parser, but it's not portable.
* Optparse provides an optparse_arg() function for stepping over
* subcommands and continuing parsing of options with another option
* string. The Optparse struct itself can be passed around to
* subcommand handlers for additional subcommand option parsing. A
* full reset can be achieved by with an additional optparse_init().
*
* 3) Error messages are printed to stderr. This can be disabled with
* opterr, but the messages themselves are still inaccessible.
* Optparse solves this by writing an error message in its errmsg
* field. The downside to Optparse is that this error message will
* always be in English rather than the current locale.
*
* Optparse should be familiar with anyone accustomed to getopt(), and
* it could be a nearly drop-in replacement. The option string is the
* same and the fields have the same names as the getopt() global
* variables (optarg, optind, optopt).
*
* Optparse also supports GNU-style long options with optparse_long().
* The interface is slightly different and simpler than getopt_long().
*
* By default, argv is permuted as it is parsed, moving non-option
* arguments to the end. This can be disabled by setting the `permute`
* field to 0 after initialization.
*/
#ifndef OPTPARSE_H
#define OPTPARSE_H
#ifndef OPTPARSE_API
# define OPTPARSE_API
#endif
struct optparse {
char **argv;
int permute;
int optind;
int optopt;
char *optarg;
char errmsg[64];
int subopt;
};
enum optparse_argtype {
OPTPARSE_NONE,
OPTPARSE_REQUIRED,
OPTPARSE_OPTIONAL
};
struct optparse_long {
const char *longname;
int shortname;
enum optparse_argtype argtype;
};
/**
* Initializes the parser state.
*/
OPTPARSE_API
void optparse_init(struct optparse *options, char **argv);
/**
* Read the next option in the argv array.
* @param optstring a getopt()-formatted option string.
* @return the next option character, -1 for done, or '?' for error
*
* Just like getopt(), a character followed by no colons means no
* argument. One colon means the option has a required argument. Two
* colons means the option takes an optional argument.
*/
OPTPARSE_API
int optparse(struct optparse *options, const char *optstring);
/**
* Handles GNU-style long options in addition to getopt() options.
* This works a lot like GNU's getopt_long(). The last option in
* longopts must be all zeros, marking the end of the array. The
* longindex argument may be NULL.
*/
OPTPARSE_API
int optparse_long(struct optparse *options,
const struct optparse_long *longopts,
int *longindex);
/**
* Used for stepping over non-option arguments.
* @return the next non-option argument, or NULL for no more arguments
*
* Argument parsing can continue with optparse() after using this
* function. That would be used to parse the options for the
* subcommand returned by optparse_arg(). This function allows you to
* ignore the value of optind.
*/
OPTPARSE_API
char *optparse_arg(struct optparse *options);
/* Implementation */
#ifdef OPTPARSE_IMPLEMENTATION
#define OPTPARSE_MSG_INVALID "invalid option"
#define OPTPARSE_MSG_MISSING "option requires an argument"
#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
static int
optparse_error(struct optparse *options, const char *msg, const char *data)
{
unsigned p = 0;
const char *sep = " -- '";
while (*msg)
options->errmsg[p++] = *msg++;
while (*sep)
options->errmsg[p++] = *sep++;
while (p < sizeof(options->errmsg) - 2 && *data)
options->errmsg[p++] = *data++;
options->errmsg[p++] = '\'';
options->errmsg[p++] = '\0';
return '?';
}
OPTPARSE_API
void
optparse_init(struct optparse *options, char **argv)
{
options->argv = argv;
options->permute = 1;
options->optind = 1;
options->subopt = 0;
options->optarg = 0;
options->errmsg[0] = '\0';
}
static int
optparse_is_dashdash(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
}
static int
optparse_is_shortopt(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
}
static int
optparse_is_longopt(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
}
static void
optparse_permute(struct optparse *options, int index)
{
char *nonoption = options->argv[index];
int i;
for (i = index; i < options->optind - 1; i++)
options->argv[i] = options->argv[i + 1];
options->argv[options->optind - 1] = nonoption;
}
static int
optparse_argtype(const char *optstring, char c)
{
int count = OPTPARSE_NONE;
if (c == ':')
return -1;
for (; *optstring && c != *optstring; optstring++);
if (!*optstring)
return -1;
if (optstring[1] == ':')
count += optstring[2] == ':' ? 2 : 1;
return count;
}
OPTPARSE_API
int
optparse(struct optparse *options, const char *optstring)
{
int type;
char *next;
char *option = options->argv[options->optind];
options->errmsg[0] = '\0';
options->optopt = 0;
options->optarg = 0;
if (option == 0) {
return -1;
} else if (optparse_is_dashdash(option)) {
options->optind++; /* consume "--" */
return -1;
} else if (!optparse_is_shortopt(option)) {
if (options->permute) {
int index = options->optind++;
int r = optparse(options, optstring);
optparse_permute(options, index);
options->optind--;
return r;
} else {
return -1;
}
}
option += options->subopt + 1;
options->optopt = option[0];
type = optparse_argtype(optstring, option[0]);
next = options->argv[options->optind + 1];
switch (type) {
case -1: {
char str[2] = {0, 0};
str[0] = option[0];
options->optind++;
return optparse_error(options, OPTPARSE_MSG_INVALID, str);
}
case OPTPARSE_NONE:
if (option[1]) {
options->subopt++;
} else {
options->subopt = 0;
options->optind++;
}
return option[0];
case OPTPARSE_REQUIRED:
options->subopt = 0;
options->optind++;
if (option[1]) {
options->optarg = option + 1;
} else if (next != 0) {
options->optarg = next;
options->optind++;
} else {
char str[2] = {0, 0};
str[0] = option[0];
options->optarg = 0;
return optparse_error(options, OPTPARSE_MSG_MISSING, str);
}
return option[0];
case OPTPARSE_OPTIONAL:
options->subopt = 0;
options->optind++;
if (option[1])
options->optarg = option + 1;
else
options->optarg = 0;
return option[0];
}
return 0;
}
OPTPARSE_API
char *
optparse_arg(struct optparse *options)
{
char *option = options->argv[options->optind];
options->subopt = 0;
if (option != 0)
options->optind++;
return option;
}
static int
optparse_longopts_end(const struct optparse_long *longopts, int i)
{
return !longopts[i].longname && !longopts[i].shortname;
}
static void
optparse_from_long(const struct optparse_long *longopts, char *optstring)
{
char *p = optstring;
int i;
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
if (longopts[i].shortname && longopts[i].shortname < 127) {
int a;
*p++ = longopts[i].shortname;
for (a = 0; a < (int)longopts[i].argtype; a++)
*p++ = ':';
}
}
*p = '\0';
}
/* Unlike strcmp(), handles options containing "=". */
static int
optparse_longopts_match(const char *longname, const char *option)
{
const char *a = option, *n = longname;
if (longname == 0)
return 0;
for (; *a && *n && *a != '='; a++, n++)
if (*a != *n)
return 0;
return *n == '\0' && (*a == '\0' || *a == '=');
}
/* Return the part after "=", or NULL. */
static char *
optparse_longopts_arg(char *option)
{
for (; *option && *option != '='; option++);
if (*option == '=')
return option + 1;
else
return 0;
}
static int
optparse_long_fallback(struct optparse *options,
const struct optparse_long *longopts,
int *longindex)
{
int result;
char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
optparse_from_long(longopts, optstring);
result = optparse(options, optstring);
if (longindex != 0) {
*longindex = -1;
if (result != -1) {
int i;
for (i = 0; !optparse_longopts_end(longopts, i); i++)
if (longopts[i].shortname == options->optopt)
*longindex = i;
}
}
return result;
}
OPTPARSE_API
int
optparse_long(struct optparse *options,
const struct optparse_long *longopts,
int *longindex)
{
int i;
char *option = options->argv[options->optind];
if (option == 0) {
return -1;
} else if (optparse_is_dashdash(option)) {
options->optind++; /* consume "--" */
return -1;
} else if (optparse_is_shortopt(option)) {
return optparse_long_fallback(options, longopts, longindex);
} else if (!optparse_is_longopt(option)) {
if (options->permute) {
int index = options->optind++;
int r = optparse_long(options, longopts, longindex);
optparse_permute(options, index);
options->optind--;
return r;
} else {
return -1;
}
}
/* Parse as long option. */
options->errmsg[0] = '\0';
options->optopt = 0;
options->optarg = 0;
option += 2; /* skip "--" */
options->optind++;
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
const char *name = longopts[i].longname;
if (optparse_longopts_match(name, option)) {
char *arg;
if (longindex)
*longindex = i;
options->optopt = longopts[i].shortname;
arg = optparse_longopts_arg(option);
if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
} if (arg != 0) {
options->optarg = arg;
} else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
options->optarg = options->argv[options->optind];
if (options->optarg == 0)
return optparse_error(options, OPTPARSE_MSG_MISSING, name);
else
options->optind++;
}
return options->optopt;
}
}
return optparse_error(options, OPTPARSE_MSG_INVALID, option);
}
#endif /* OPTPARSE_IMPLEMENTATION */
#endif /* OPTPARSE_H */