Native shebang: Difference between revisions

From Rosetta Code
Content added Content deleted
m (minor tidy)
m (→‎{{header|C}}: use std libs EXIT_SUCCESS etc)
Line 30: Line 30:
#include <unistd.h>
#include <unistd.h>


/* the shebang is:
/* the shebang is:
* * #!/usr/local/bin/script_gcc
* * #!/usr/local/bin/script_gcc
* * */
* * */


/* general readability constants */
/* general readability constants */
typedef char /* const */ *STRING;
typedef char /* const */ *STRING;
typedef enum{FALSE=0, TRUE=1}BOOL;
typedef enum{FALSE=0, TRUE=1} BOOL;
const STRING ENDCAT = NULL;
enum {RETURNED_OK=0, RETURNED_FAIL=-1} RETURNED;
const STRING EMPTY = NULL,
ENDCAT = NULL;


/* gcc_script.c specific constants */
/* gcc_script.c specific constants */
#define DIALECT "c" /* or cpp */
#define DIALECT "c" /* or cpp */
const STRING
const STRING
CC="gcc",
CC="gcc",
COPTS="-lm -x "DIALECT,
COPTS="-lm -x "DIALECT,
IEXT="."DIALECT,
IEXT="."DIALECT,
OEXT=".out";
OEXT=".out";
const BOOL OPT_CACHE = TRUE;
const BOOL OPT_CACHE = TRUE;
Line 60: Line 58:
STRING arg;
STRING arg;
strcat_out[0]='\0';
strcat_out[0]='\0';
for(arg=argv; arg!=ENDCAT; arg=va_arg(ap, STRING)){
for(arg=argv; arg != ENDCAT; arg=va_arg(ap, STRING)){
strncat(strcat_out, arg, sizeof strcat_out);
strncat(strcat_out, arg, sizeof strcat_out);
}
}
Line 76: Line 74:
time_t modtime(STRING filename){
time_t modtime(STRING filename){
struct stat buf;
struct stat buf;
if(stat(filename, &buf)==RETURNED_FAIL)perror(filename);
if(stat(filename, &buf) != EXIT_SUCCESS)perror(filename);
return buf.st_mtime;
return buf.st_mtime;
}
}
Line 84: Line 82:
int out;
int out;
STRING compiler_command=STRCAT(CC, " ", COPTS, " -o ", binpath, " -", ENDCAT);
STRING compiler_command=STRCAT(CC, " ", COPTS, " -o ", binpath, " -", ENDCAT);
FILE *src=fopen(srcpath, "r"),
FILE *src=fopen(srcpath, "r"),
*compiler=popen(compiler_command, "w");
*compiler=popen(compiler_command, "w");
char buf[BUFSIZ];
char buf[BUFSIZ];
Line 98: Line 96:
void main(int argc, STRING *argv, STRING *envp){
void main(int argc, STRING *argv, STRING *envp){


STRING binpath,
STRING binpath,
srcpath=argv[1],
srcpath=argv[1],
argv0_basename=STRCAT(basename((char*)srcpath /*, .DIALECT */), ENDCAT),
argv0_basename=STRCAT(basename((char*)srcpath /*, .DIALECT */), ENDCAT),
*dirnamew, *dirnamex;
*dirnamew, *dirnamex;
argv++;
argv++;



/* Warning: current dir "." is in path, AND all/tmp directories are common/shared */
/* Warning: current dir "." is in path, AND * /tmp directories are common/shared */
STRING paths[] = {
STRING paths[] ={dirname(strdup(srcpath)), STRCAT(getenv("HOME"), "/bin", ENDCAT), "/usr/local/bin",
dirname(strdup(srcpath)), /* not sure why strdup is required? */
".", STRCAT(getenv("HOME"), "/tmp", ENDCAT), "/tmp", ENDCAT};
STRCAT(getenv("HOME"), "/bin", ENDCAT),
"/usr/local/bin",
".",
STRCAT(getenv("HOME"), "/tmp", ENDCAT),
getenv("HOME"),
STRCAT(getenv("HOME"), "/Desktop", ENDCAT),
/* "/tmp" ... a bit of a security hole */
ENDCAT
};


for(dirnamew = paths; *dirnamew; dirnamew++){
for(dirnamew = paths; *dirnamew; dirnamew++){
if(access(*dirnamew, W_OK)==RETURNED_OK) break;
if(access(*dirnamew, W_OK) == EXIT_SUCCESS) break;
}
}

/* if a CACHEd copy is not to be kept, then fork a sub-process to unlink the .out file */
/* if a CACHEd copy is not to be kept, then fork a sub-process to unlink the .out file */
if(OPT_CACHE == FALSE){
if(OPT_CACHE == FALSE){
binpath=STRCAT(*dirnamew, "/", argv0_basename, itoa(getpid()), OEXT, ENDCAT);
binpath=STRCAT(*dirnamew, "/", argv0_basename, itoa(getpid()), OEXT, ENDCAT);
if(compile(srcpath, binpath) == RETURNED_OK){
if(compile(srcpath, binpath) == EXIT_SUCCESS){
if(fork()){
if(fork()){
sleep(0.1); unlink(binpath);
sleep(0.1); unlink(binpath);
Line 128: Line 135:
for(dirnamex = paths; *dirnamex; dirnamex++){
for(dirnamex = paths; *dirnamex; dirnamex++){
binpath=STRCAT(*dirnamex, "/", argv0_basename, OEXT, ENDCAT);
binpath=STRCAT(*dirnamex, "/", argv0_basename, OEXT, ENDCAT);
if((access(binpath, X_OK) == RETURNED_OK) && (modtime(binpath) >= modtime_srcpath))
if((access(binpath, X_OK) == EXIT_SUCCESS) && (modtime(binpath) >= modtime_srcpath))
execvp(binpath, argv);
execvp(binpath, argv);
}
}
Line 134: Line 141:


binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT);
binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT);
if(compile(srcpath, binpath) == RETURNED_OK)
if(compile(srcpath, binpath) == EXIT_SUCCESS)
execvp(binpath, argv);
execvp(binpath, argv);


Line 145: Line 152:
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>


main(int argc, char **argv, char **envp){
int main(int argc, char **argv, char **envp){
char ofs = '\0';
char ofs = '\0';
for(argv++; *argv; argv++){
for(argv++; *argv; argv++){
Line 153: Line 161:
}
}
putchar('\n');
putchar('\n');
exit(EXIT_SUCCESS);
}</lang>
}</lang>



Revision as of 00:57, 3 September 2013

Native shebang is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Simple shebangs can help with scripting, e.g. "#!/usr/bin/env python" at the top of a Python script will allow it to be run in a terminal as "./script.py".

The task Multiline shebang largely demonstrates how to use "shell" code in the shebang to compile and/or run source-code from a 3rd language.

However in this task Native shebang task we are go native. In the shebang, instead of running a shell, we call a binary-executable generated from the original native language, e.g. "#!/usr/local/bin/script_gcc" to extract, compile and run the native "script" source code.

Other small innovations required of this Native shebang task:

  • Cache the executable in some appropriate place in a path, dependant on available write permissions.
  • Generate the cached executable only when the source has been touched.
  • If a cached is available, then run this instead of regenerating a new executable.

Difficulties:

  • Naturally, some languages are not compiled. These languages are forced to use shebang executables from another language, eg "#!/usr/bin/env python" uses the C binaries /usr/bin/env and /usr/bin/python. If this is the case, then simply document the details of the case.
  • In a perfect world, the test file (e.g. echo.c) would still be a valid program, and would compile without error using the native compiler (e.g. gcc for text.c). The problem is that "#!" is syntactically incorrect on many languages, but in others it can be parsed as a comment.
  • The "test binary" should be exec-ed and hence retain the original Process identifier.

Test case:

  • Create a simple "script file" (in the same native language) called "echo" then use the "script" to output "Hello, world!"

C

File: script_gcc.c <lang c>#include <errno.h>

  1. include <libgen.h>
  2. include <stdarg.h>
  3. include <stdio.h>
  4. include <stdlib.h>
  5. include <string.h>
  6. include <sys/stat.h>
  7. include <unistd.h>

/* the shebang is:

*  * #!/usr/local/bin/script_gcc
*   * */

/* general readability constants */ typedef char /* const */ *STRING; typedef enum{FALSE=0, TRUE=1} BOOL; const STRING ENDCAT = NULL;

/* gcc_script.c specific constants */

  1. define DIALECT "c" /* or cpp */

const STRING

 CC="gcc",
 COPTS="-lm -x "DIALECT,
 IEXT="."DIALECT,
 OEXT=".out";

const BOOL OPT_CACHE = TRUE;

/* general utility procedured */ char strcat_out[BUFSIZ];

STRING srcpath;

STRING STRCAT(STRING argv, ... ){

 va_list ap;
 va_start(ap, argv);
 STRING arg;
 strcat_out[0]='\0';
 for(arg=argv; arg != ENDCAT; arg=va_arg(ap, STRING)){
    strncat(strcat_out, arg, sizeof strcat_out);
 }
 va_end(ap);
 return strndup(strcat_out, sizeof strcat_out);

}

char itoa_out[BUFSIZ];

STRING itoa(int i){

 sprintf(itoa_out, "%d", i);
 return itoa_out;

}

time_t modtime(STRING filename){

 struct stat buf;
 if(stat(filename, &buf) != EXIT_SUCCESS)perror(filename);
 return buf.st_mtime;

}

/* gcc_script specific procedure */ BOOL compile(STRING srcpath, STRING binpath){

 int out;
 STRING compiler_command=STRCAT(CC, " ", COPTS, " -o ", binpath, " -", ENDCAT);
 FILE *src=fopen(srcpath, "r"),
      *compiler=popen(compiler_command, "w");
 char buf[BUFSIZ];
 BOOL shebang;
 for(shebang=TRUE; fgets(buf, sizeof buf, src); shebang=FALSE)
   if(!shebang)fwrite(buf, strlen(buf), 1, compiler);
 out=pclose(compiler);
 return out;

}

void main(int argc, STRING *argv, STRING *envp){

 STRING binpath,
        srcpath=argv[1],
        argv0_basename=STRCAT(basename((char*)srcpath /*, .DIALECT */), ENDCAT),
        *dirnamew, *dirnamex;
 argv++;


/* Warning: current dir "." is in path, AND * /tmp directories are common/shared */

 STRING paths[] = {
   dirname(strdup(srcpath)), /* not sure why strdup is required? */
   STRCAT(getenv("HOME"), "/bin", ENDCAT),
   "/usr/local/bin",
   ".",
   STRCAT(getenv("HOME"), "/tmp", ENDCAT),
   getenv("HOME"),
   STRCAT(getenv("HOME"), "/Desktop", ENDCAT),

/* "/tmp" ... a bit of a security hole */

   ENDCAT
 };
 for(dirnamew = paths; *dirnamew; dirnamew++){
   if(access(*dirnamew, W_OK) == EXIT_SUCCESS) break;
 }

/* if a CACHEd copy is not to be kept, then fork a sub-process to unlink the .out file */

 if(OPT_CACHE == FALSE){
   binpath=STRCAT(*dirnamew, "/", argv0_basename, itoa(getpid()), OEXT, ENDCAT);
   if(compile(srcpath, binpath) == EXIT_SUCCESS){
     if(fork()){
       sleep(0.1); unlink(binpath);
     } else {
       execvp(binpath, argv);
     }
   }
 } else {

/* else a CACHEd copy is kept, so find it */

   time_t modtime_srcpath = modtime(srcpath);
   for(dirnamex = paths; *dirnamex; dirnamex++){
     binpath=STRCAT(*dirnamex, "/", argv0_basename, OEXT, ENDCAT);
     if((access(binpath, X_OK) == EXIT_SUCCESS) && (modtime(binpath) >= modtime_srcpath))
       execvp(binpath, argv);
   }
 }
 binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT);
 if(compile(srcpath, binpath) == EXIT_SUCCESS)
   execvp(binpath, argv);
 perror(STRCAT(binpath, ": executable not available", ENDCAT));
 exit(errno);

}</lang>

Test Source File: echo.c <lang c>#!/usr/local/bin/script_gcc

  1. include <stdio.h>
  2. include <string.h>
  3. include <stdlib.h>

int main(int argc, char **argv, char **envp){

 char ofs = '\0';
 for(argv++; *argv; argv++){
   if(ofs)putchar(ofs); else ofs=' ';
   fwrite(*argv, strlen(*argv), 1, stdout);
 }
 putchar('\n');
 exit(EXIT_SUCCESS);

}</lang>

Test Execution:

$ ./echo.c Hello, world! 

Test Output:

Hello, world!