/* amigaos.c uses only amigaos APIs, * as opposed to amigaio.c which mixes amigaos and perl APIs */ #include #include #include #include #include #include #include #if defined(__CLIB2__) # include #endif #if defined(__NEWLIB__) # include #endif #include #include #include #include #undef WORD #define WORD int16 #include #include #include #include #include "amigaos.h" struct UtilityIFace *IUtility = NULL; /***************************************************************************/ struct Interface *OpenInterface(CONST_STRPTR libname, uint32 libver) { struct Library *base = IExec->OpenLibrary(libname, libver); struct Interface *iface = IExec->GetInterface(base, "main", 1, NULL); if (iface == NULL) { // We should probably post some kind of error message here. IExec->CloseLibrary(base); } return iface; } /***************************************************************************/ void CloseInterface(struct Interface *iface) { if (iface != NULL) { struct Library *base = iface->Data.LibBase; IExec->DropInterface(iface); IExec->CloseLibrary(base); } } BOOL __unlink_retries = FALSE; void ___makeenviron() __attribute__((constructor)); void ___freeenviron() __attribute__((destructor)); void ___openinterfaces() __attribute__((constructor)); void ___closeinterfaces() __attribute__((destructor)); void ___openinterfaces() { if (!IDOS) IDOS = (struct DOSIFace *)OpenInterface("dos.library", 53); if (!IUtility) IUtility = (struct UtilityIFace *)OpenInterface("utility.library", 53); } void ___closeinterfaces() { CloseInterface((struct Interface *)IDOS); CloseInterface((struct Interface *)IUtility); } int VARARGS68K araddebug(UBYTE *fmt, ...); int VARARGS68K adebug(UBYTE *fmt, ...); #define __USE_RUNCOMMAND__ char **myenviron = NULL; char **origenviron = NULL; static void createvars(char **envp); struct args { BPTR seglist; int stack; char *command; int length; int result; char **envp; }; int __myrc(char *arg) { struct Task *thisTask = IExec->FindTask(0); struct args *myargs = (struct args *)thisTask->tc_UserData; if (myargs->envp) createvars(myargs->envp); // adebug("%s %ld %s \n",__FUNCTION__,__LINE__,myargs->command); myargs->result = IDOS->RunCommand(myargs->seglist, myargs->stack, myargs->command, myargs->length); return 0; } int32 myruncommand( BPTR seglist, int stack, char *command, int length, char **envp) { struct args myargs; struct Task *thisTask = IExec->FindTask(0); struct Process *proc; // adebug("%s %ld %s\n",__FUNCTION__,__LINE__,command?command:"NULL"); myargs.seglist = seglist; myargs.stack = stack; myargs.command = command; myargs.length = length; myargs.result = -1; myargs.envp = envp; if ((proc = IDOS->CreateNewProcTags( NP_Entry, __myrc, NP_Child, TRUE, NP_Input, IDOS->Input(), NP_Output, IDOS->Output(), NP_Error, IDOS->ErrorOutput(), NP_CloseInput, FALSE, NP_CloseOutput, FALSE, NP_CloseError, FALSE, NP_CopyVars, FALSE, // NP_StackSize, ((struct Process // *)myargs.parent)->pr_StackSize, NP_Cli, TRUE, NP_UserData, (int)&myargs, NP_NotifyOnDeathSigTask, thisTask, TAG_DONE))) { IExec->Wait(SIGF_CHILD); } return myargs.result; } char *mystrdup(const char *s) { char *result = NULL; size_t size; size = strlen(s) + 1; if ((result = (char *)IExec->AllocVec(size, MEMF_ANY))) { memmove(result, s, size); } return result; } static int pipenum = 0; int pipe(int filedes[2]) { char pipe_name[1024]; // adebug("%s %ld \n",__FUNCTION__,__LINE__); #ifdef USE_TEMPFILES sprintf(pipe_name, "/T/%x.%08lx", pipenum++, IUtility->GetUniqueID()); #else sprintf(pipe_name, "/PIPE/%x%08lx/4096/0", pipenum++, IUtility->GetUniqueID()); #endif /* printf("pipe: %s \n", pipe_name);*/ filedes[1] = open(pipe_name, O_WRONLY | O_CREAT); filedes[0] = open(pipe_name, O_RDONLY); if (filedes[0] == -1 || filedes[1] == -1) { if (filedes[0] != -1) close(filedes[0]); if (filedes[1] != -1) close(filedes[1]); return -1; } /* printf("filedes %d %d\n", filedes[0], * filedes[1]);fflush(stdout);*/ return 0; } int fork(void) { fprintf(stderr, "Can not bloody fork\n"); errno = ENOMEM; return -1; } int wait(int *status) { fprintf(stderr, "No wait try waitpid instead\n"); errno = ECHILD; return -1; } char *convert_path_a2u(const char *filename) { struct NameTranslationInfo nti; if (!filename) { return 0; } __translate_amiga_to_unix_path_name(&filename, &nti); return mystrdup(filename); } char *convert_path_u2a(const char *filename) { struct NameTranslationInfo nti; if (!filename) { return 0; } if (strcmp(filename, "/dev/tty") == 0) { return mystrdup("CONSOLE:"); ; } __translate_unix_to_amiga_path_name(&filename, &nti); return mystrdup(filename); } static struct SignalSemaphore environ_sema; void amigaos4_init_environ_sema() { IExec->InitSemaphore(&environ_sema); } void amigaos4_obtain_environ() { IExec->ObtainSemaphore(&environ_sema); } void amigaos4_release_environ() { IExec->ReleaseSemaphore(&environ_sema); } static void createvars(char **envp) { if (envp) { /* Set a local var to indicate to any subsequent sh that it is * not * the top level shell and so should only inherit local amigaos * vars */ IDOS->SetVar("ABCSH_IMPORT_LOCAL", "TRUE", 5, GVF_LOCAL_ONLY); amigaos4_obtain_environ(); envp = myenviron; while ((envp != NULL) && (*envp != NULL)) { int len; char *var; char *val; if ((len = strlen(*envp))) { if ((var = (char *)IExec->AllocVec( len + 1, MEMF_ANY | MEMF_CLEAR))) { strcpy(var, *envp); val = strchr(var, '='); if (val) { *val++ = '\0'; if (*val) { IDOS->SetVar( var, val, strlen(val) + 1, GVF_LOCAL_ONLY); } } IExec->FreeVec(var); } } envp++; } amigaos4_release_environ(); } } static BOOL contains_whitespace(char *string) { if (string) { if (strchr(string, ' ')) return TRUE; if (strchr(string, '\t')) return TRUE; if (strchr(string, '\n')) return TRUE; if (strchr(string, 0xA0)) return TRUE; if (strchr(string, '"')) return TRUE; } return FALSE; } static int no_of_escapes(char *string) { int cnt = 0; char *p; for (p = string; p < string + strlen(string); p++) { if (*p == '"') cnt++; if (*p == '*') cnt++; if (*p == '\n') cnt++; if (*p == '\t') cnt++; } return cnt; } struct command_data { STRPTR args; BPTR seglist; struct Task *parent; }; int myexecvp(bool isperlthread, const char *filename, char *argv[]) { // adebug("%s %ld //%s\n",__FUNCTION__,__LINE__,filename?filename:"NULL"); /* if there's a slash or a colon consider filename a path and skip * search */ int res; if ((strchr(filename, '/') == NULL) && (strchr(filename, ':') == NULL)) { char *path; char *name; char *pathpart; char *p; size_t len; struct stat st; if (!(path = getenv("PATH"))) { path = ".:/bin:/usr/bin:/c"; } len = strlen(filename) + 1; name = (char *)alloca(strlen(path) + len); pathpart = (char *)alloca(strlen(path) + 1); p = path; do { path = p; if (!(p = strchr(path, ':'))) { p = strchr(path, '\0'); } memcpy(pathpart, path, p - path); pathpart[p - path] = '\0'; if (!(strlen(pathpart) == 0)) { sprintf(name, "%s/%s", pathpart, filename); } else sprintf(name, "%s", filename); if ((stat(name, &st) == 0) && (S_ISREG(st.st_mode))) { /* we stated it and it's a regular file */ /* let's boogie! */ filename = name; break; } } while (*p++ != '\0'); } res = myexecve(isperlthread, filename, argv, myenviron); return res; } int myexecv(bool isperlthread, const char *path, char *argv[]) { return myexecve(isperlthread, path, argv, myenviron); } int myexecl(bool isperlthread, const char *path, ...) { va_list va; char *argv[1024]; /* 1024 enough? let's hope so! */ int i = 0; // adebug("%s %ld\n",__FUNCTION__,__LINE__); va_start(va, path); i = 0; do { argv[i] = va_arg(va, char *); } while (argv[i++] != NULL); va_end(va); return myexecve(isperlthread, path, argv, myenviron); } #if 0 int myexecve(const char *filename, char *argv[], char *envp[]) { FILE *fh; char buffer[1000]; int size = 0; char **cur; char *interpreter = 0; char *interpreter_args = 0; char *full = 0; char *filename_conv = 0; char *interpreter_conv = 0; // char *tmp = 0; char *fname; // int tmpint; // struct Task *thisTask = IExec->FindTask(0); int result = -1; StdioStore store; dTHX; if(aTHX) // I hope this is NULL when not on a interpreteer thread nor to level. { /* Save away our stdio */ amigaos_stdio_save(aTHX_ & store); } // adebug("%s %ld %s\n",__FUNCTION__,__LINE__,filename?filename:"NULL"); /* Calculate the size of filename and all args, including spaces and * quotes */ size = 0; // strlen(filename) + 1; for (cur = (char **)argv /* +1 */; *cur; cur++) { size += strlen(*cur) + 1 + (contains_whitespace(*cur) ? (2 + no_of_escapes(*cur)) : 0); } /* Check if it's a script file */ fh = fopen(filename, "r"); if (fh) { if (fgetc(fh) == '#' && fgetc(fh) == '!') { char *p; char *q; fgets(buffer, 999, fh); p = buffer; while (*p == ' ' || *p == '\t') p++; if (buffer[strlen(buffer) - 1] == '\n') buffer[strlen(buffer) - 1] = '\0'; if ((q = strchr(p, ' '))) { *q++ = '\0'; if (*q != '\0') { interpreter_args = mystrdup(q); } } else interpreter_args = mystrdup(""); interpreter = mystrdup(p); size += strlen(interpreter) + 1; size += strlen(interpreter_args) + 1; } fclose(fh); } else { /* We couldn't open this why not? */ if (errno == ENOENT) { /* file didn't exist! */ goto out; } } /* Allocate the command line */ filename_conv = convert_path_u2a(filename); if (filename_conv) size += strlen(filename_conv); size += 1; full = (char *)IExec->AllocVec(size + 10, MEMF_ANY | MEMF_CLEAR); if (full) { if (interpreter) { interpreter_conv = convert_path_u2a(interpreter); #if !defined(__USE_RUNCOMMAND__) #warning(using system!) sprintf(full, "%s %s %s ", interpreter_conv, interpreter_args, filename_conv); #else sprintf(full, "%s %s ", interpreter_args, filename_conv); #endif IExec->FreeVec(interpreter); IExec->FreeVec(interpreter_args); if (filename_conv) IExec->FreeVec(filename_conv); fname = mystrdup(interpreter_conv); if (interpreter_conv) IExec->FreeVec(interpreter_conv); } else { #ifndef __USE_RUNCOMMAND__ sprintf(full, "%s ", filename_conv); #else sprintf(full, ""); #endif fname = mystrdup(filename_conv); if (filename_conv) IExec->FreeVec(filename_conv); } for (cur = (char **)(argv + 1); *cur != 0; cur++) { if (contains_whitespace(*cur)) { int esc = no_of_escapes(*cur); if (esc > 0) { char *buff = IExec->AllocVec( strlen(*cur) + 4 + esc, MEMF_ANY | MEMF_CLEAR); char *p = *cur; char *q = buff; *q++ = '"'; while (*p != '\0') { if (*p == '\n') { *q++ = '*'; *q++ = 'N'; p++; continue; } else if (*p == '"') { *q++ = '*'; *q++ = '"'; p++; continue; } else if (*p == '*') { *q++ = '*'; } *q++ = *p++; } *q++ = '"'; *q++ = ' '; *q = '\0'; strcat(full, buff); IExec->FreeVec(buff); } else { strcat(full, "\""); strcat(full, *cur); strcat(full, "\" "); } } else { strcat(full, *cur); strcat(full, " "); } } strcat(full, "\n"); // if(envp) // createvars(envp); #ifndef __USE_RUNCOMMAND__ result = IDOS->SystemTags( full, SYS_UserShell, TRUE, NP_StackSize, ((struct Process *)thisTask)->pr_StackSize, SYS_Input, ((struct Process *)thisTask)->pr_CIS, SYS_Output, ((struct Process *)thisTask)->pr_COS, SYS_Error, ((struct Process *)thisTask)->pr_CES, TAG_DONE); #else if (fname) { BPTR seglist = IDOS->LoadSeg(fname); if (seglist) { /* check if we have an executable! */ struct PseudoSegList *ps = NULL; if (!IDOS->GetSegListInfoTags( seglist, GSLI_Native, &ps, TAG_DONE)) { IDOS->GetSegListInfoTags( seglist, GSLI_68KPS, &ps, TAG_DONE); } if (ps != NULL) { // adebug("%s %ld %s // %s\n",__FUNCTION__,__LINE__,fname,full); IDOS->SetCliProgramName(fname); // result=RunCommand(seglist,8*1024,full,strlen(full)); // result=myruncommand(seglist,8*1024,full,strlen(full),envp); result = myruncommand(seglist, 8 * 1024, full, -1, envp); errno = 0; } else { errno = ENOEXEC; } IDOS->UnLoadSeg(seglist); } else { errno = ENOEXEC; } IExec->FreeVec(fname); } #endif /* USE_RUNCOMMAND */ IExec->FreeVec(full); if (errno == ENOEXEC) { result = -1; } goto out; } if (interpreter) IExec->FreeVec(interpreter); if (filename_conv) IExec->FreeVec(filename_conv); errno = ENOMEM; out: amigaos_stdio_restore(aTHX_ &store); STATUS_NATIVE_CHILD_SET(result); PL_exit_flags |= PERL_EXIT_EXPECTED; if (result != -1) my_exit(result); return(result); } #endif int pause(void) { fprintf(stderr, "Pause not implemented\n"); errno = EINTR; return -1; } uint32 size_env(struct Hook *hook, APTR userdata, struct ScanVarsMsg *message) { if (strlen(message->sv_GDir) <= 4) { hook->h_Data = (APTR)(((uint32)hook->h_Data) + 1); } return 0; } uint32 copy_env(struct Hook *hook, APTR userdata, struct ScanVarsMsg *message) { if (strlen(message->sv_GDir) <= 4) { char **env = (char **)hook->h_Data; uint32 size = strlen(message->sv_Name) + 1 + message->sv_VarLen + 1 + 1; char *buffer = (char *)IExec->AllocVec((uint32)size, MEMF_ANY | MEMF_CLEAR); snprintf(buffer, size - 1, "%s=%s", message->sv_Name, message->sv_Var); *env = buffer; env++; hook->h_Data = env; } return 0; } void ___makeenviron() { struct Hook hook; char varbuf[8]; uint32 flags = 0; struct DOSIFace *myIDOS = (struct DOSIFace *)OpenInterface("dos.library", 53); if (myIDOS) { if (myIDOS->GetVar("ABCSH_IMPORT_LOCAL", varbuf, 8, GVF_LOCAL_ONLY) > 0) { flags = GVF_LOCAL_ONLY; } else { flags = GVF_GLOBAL_ONLY; } hook.h_Entry = size_env; hook.h_Data = 0; myIDOS->ScanVars(&hook, flags, 0); hook.h_Data = (APTR)(((uint32)hook.h_Data) + 1); myenviron = (char **)IExec->AllocVec((uint32)hook.h_Data * sizeof(char **), MEMF_ANY | MEMF_CLEAR); origenviron = myenviron; if (!myenviron) { return; } hook.h_Entry = copy_env; hook.h_Data = myenviron; myIDOS->ScanVars(&hook, flags, 0); CloseInterface((struct Interface *)myIDOS); } } void ___freeenviron() { char **i; /* perl might change environ, it puts it back except for ctrl-c */ /* so restore our own copy here */ struct DOSIFace *myIDOS = (struct DOSIFace *)OpenInterface("dos.library", 53); if (myIDOS) { myenviron = origenviron; if (myenviron) { for (i = myenviron; *i != NULL; i++) { IExec->FreeVec(*i); } IExec->FreeVec(myenviron); myenviron = NULL; } CloseInterface((struct Interface *)myIDOS); } } /* reimplementation of popen, clib2's doesn't do all we want */ static BOOL is_final_quote_character(const char *str) { BOOL result; result = (BOOL)(str[0] == '\"' && (str[1] == '\0' || isspace(str[1]))); return (result); } static BOOL is_final_squote_character(const char *str) { BOOL result; result = (BOOL)(str[0] == '\'' && (str[1] == '\0' || isspace(str[1]))); return (result); } int popen_child() { struct Task *thisTask = IExec->FindTask(0); char *command = (char *)thisTask->tc_UserData; size_t len; char *str; int argc; int number_of_arguments; char *argv[4]; argv[0] = "sh"; argv[1] = "-c"; argv[2] = command ? command : NULL; argv[3] = NULL; // adebug("%s %ld %s\n",__FUNCTION__,__LINE__,command?command:"NULL"); /* We need to give this to sh via execvp, execvp expects filename, * argv[] */ myexecvp(FALSE, argv[0], argv); if (command) IExec->FreeVec(command); IExec->Forbid(); return 0; } FILE *amigaos_popen(const char *cmd, const char *mode) { FILE *result = NULL; char pipe_name[50]; char unix_pipe[50]; char ami_pipe[50]; char *cmd_copy; BPTR input = 0; BPTR output = 0; struct Process *proc = NULL; struct Task *thisTask = IExec->FindTask(0); /* First we need to check the mode * We can only have unidirectional pipes */ // adebug("%s %ld cmd %s mode %s \n",__FUNCTION__,__LINE__,cmd, // mode); switch (mode[0]) { case 'r': case 'w': break; default: errno = EINVAL; return result; } /* Make a unique pipe name * we need a unix one and an amigaos version (of the same pipe!) * as were linking with libunix. */ sprintf(pipe_name, "%x%08lx/4096/0", pipenum++, IUtility->GetUniqueID()); sprintf(unix_pipe, "/PIPE/%s", pipe_name); sprintf(ami_pipe, "PIPE:%s", pipe_name); /* Now we open the AmigaOs Filehandles That we wil pass to our * Sub process */ if (mode[0] == 'r') { /* A read mode pipe: Output from pipe input from NIL:*/ input = IDOS->Open("NIL:", MODE_NEWFILE); if (input != 0) { output = IDOS->Open(ami_pipe, MODE_NEWFILE); } } else { input = IDOS->Open(ami_pipe, MODE_NEWFILE); if (input != 0) { output = IDOS->Open("NIL:", MODE_NEWFILE); } } if ((input == 0) || (output == 0)) { /* Ouch stream opening failed */ /* Close and bail */ if (input) IDOS->Close(input); if (output) IDOS->Close(output); return result; } /* We have our streams now start our new process * We're using a new process so that execve can modify the environment * with messing things up for the shell that launched perl * Copy cmd before we launch the subprocess as perl seems to waste * no time in overwriting it! The subprocess will free the copy. */ if ((cmd_copy = mystrdup(cmd))) { // adebug("%s %ld // %s\n",__FUNCTION__,__LINE__,cmd_copy?cmd_copy:"NULL"); proc = IDOS->CreateNewProcTags( NP_Entry, popen_child, NP_Child, TRUE, NP_StackSize, ((struct Process *)thisTask)->pr_StackSize, NP_Input, input, NP_Output, output, NP_Error, IDOS->ErrorOutput(), NP_CloseError, FALSE, NP_Cli, TRUE, NP_Name, "Perl: popen process", NP_UserData, (int)cmd_copy, TAG_DONE); } if (!proc) { /* New Process Failed to start * Close and bail out */ if (input) IDOS->Close(input); if (output) IDOS->Close(output); if (cmd_copy) IExec->FreeVec(cmd_copy); } /* Our new process is running and will close it streams etc * once its done. All we need to is open the pipe via stdio */ return fopen(unix_pipe, mode); } /* Work arround for clib2 fstat */ #ifndef S_IFCHR #define S_IFCHR 0x0020000 #endif #define SET_FLAG(u, v) ((void)((u) |= (v))) int afstat(int fd, struct stat *statb) { int result; BPTR fh; int mode; BOOL input; /* In the first instance pass it to fstat */ // adebug("fd %ld ad %ld\n",fd,amigaos_get_file(fd)); if ((result = fstat(fd, statb) >= 0)) return result; /* Now we've got a file descriptor but we failed to stat it */ /* Could be a nil: or could be a std#? */ /* if get_default_file fails we had a dud fd so return failure */ #if !defined(__CLIB2__) fh = amigaos_get_file(fd); /* if nil: return failure*/ if (fh == 0) return -1; /* Now compare with our process Input() Output() etc */ /* if these were regular files sockets or pipes we had already * succeeded */ /* so we can guess they a character special console.... I hope */ struct ExamineData *data; char name[120]; name[0] = '\0'; data = IDOS->ExamineObjectTags(EX_FileHandleInput, fh, TAG_END); if (data != NULL) { IUtility->Strlcpy(name, data->Name, sizeof(name)); IDOS->FreeDosObject(DOS_EXAMINEDATA, data); } // adebug("ad %ld '%s'\n",amigaos_get_file(fd),name); mode = S_IFCHR; if (fh == IDOS->Input()) { input = TRUE; SET_FLAG(mode, S_IRUSR); SET_FLAG(mode, S_IRGRP); SET_FLAG(mode, S_IROTH); } else if (fh == IDOS->Output() || fh == IDOS->ErrorOutput()) { input = FALSE; SET_FLAG(mode, S_IWUSR); SET_FLAG(mode, S_IWGRP); SET_FLAG(mode, S_IWOTH); } else { /* we got a filehandle not handle by fstat or the above */ /* most likely it's NIL: but lets check */ struct ExamineData *exd = NULL; if ((exd = IDOS->ExamineObjectTags(EX_FileHandleInput, fh, TAG_DONE))) { BOOL isnil = FALSE; if (exd->Type == (20060920)) // Ugh yes I know nasty..... { isnil = TRUE; } IDOS->FreeDosObject(DOS_EXAMINEDATA, exd); if (isnil) { /* yep we got NIL: */ SET_FLAG(mode, S_IRUSR); SET_FLAG(mode, S_IRGRP); SET_FLAG(mode, S_IROTH); SET_FLAG(mode, S_IWUSR); SET_FLAG(mode, S_IWGRP); SET_FLAG(mode, S_IWOTH); } else { IExec->DebugPrintF( "unhandled filehandle in afstat()\n"); return -1; } } } memset(statb, 0, sizeof(statb)); statb->st_mode = mode; #endif return 0; } BPTR amigaos_get_file(int fd) { BPTR fh = (BPTR)NULL; if (!(fh = _get_osfhandle(fd))) { switch (fd) { case 0: fh = IDOS->Input(); break; case 1: fh = IDOS->Output(); break; case 2: fh = IDOS->ErrorOutput(); break; default: break; } } return fh; } /*########################################################################*/ #define LOCK_START 0xFFFFFFFFFFFFFFFELL #define LOCK_LENGTH 1LL // No wait forever option so lets wait for a loooong time. #define TIMEOUT 0x7FFFFFFF int amigaos_flock(int fd, int oper) { BPTR fh; int32 success = -1; if (!(fh = amigaos_get_file(fd))) { errno = EBADF; return -1; } switch (oper) { case LOCK_SH: { if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH, REC_SHARED | RECF_DOS_METHOD_ONLY, TIMEOUT)) { success = 0; } break; } case LOCK_EX: { if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH, REC_EXCLUSIVE | RECF_DOS_METHOD_ONLY, TIMEOUT)) { success = 0; } break; } case LOCK_SH | LOCK_NB: { if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH, REC_SHARED_IMMED | RECF_DOS_METHOD_ONLY, TIMEOUT)) { success = 0; } else { errno = EWOULDBLOCK; } break; } case LOCK_EX | LOCK_NB: { if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH, REC_EXCLUSIVE_IMMED | RECF_DOS_METHOD_ONLY, TIMEOUT)) { success = 0; } else { errno = EWOULDBLOCK; } break; } case LOCK_UN: { if (IDOS->UnLockRecord(fh, LOCK_START, LOCK_LENGTH)) { success = 0; } break; } default: { errno = EINVAL; return -1; } } return success; }