Native shebang: Difference between revisions

Content added Content deleted
(Added 2nd version of C source. This one is 'full' C and does not cheat with a bash shell script.)
Line 204: Line 204:
'''Test Source File: echo.c'''
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc.c
<lang c>#!/usr/local/bin/script_gcc.c
#include <stdio.h>
#include <string.h>
#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:'''
<pre>
$ ./echo.c Hello, world!
</pre>
{{out}}
<pre>
Hello, world!
</pre>


'''File: script_gcc.c (2nd version) Pure C, requires no shell script'''
<lang c>/*
* rosettacode.org: Native shebang
*
* Copyright 2015, Jim Fougeron. Code placed in public domain. If you
* use this, please provide attribution to author. Code originally came
* from the code found on rosettacode.org/wiki/Native_shebang, however
* the code was about 80% rewritten. But the logic of the compile()
* function still strongly is based upon original code, and is a key
* part of the file.
*
* Native C language shebang scripting. Build this program to /usr/local/bin
* using:
* gcc -o/usr/local/bin/script_gcc script_gcc.c
*
* The name of the executable: "script_gcc" IS critical. It is used in knowing
* that we have have found the proper shebang file when parsing commandline
*
* the actual shebang for executable C source scripts is:

#!/usr/local/bin/script_gcc [extra compile/link options]

* If there additional lib's needed by your source file, then add the proper
* params to the shebang line. So for instance if gmp, openssl, and zlib
* were needed (and an additional include path), you would use this shebang:

#!/usr/local/bin/script_gcc -lgmp -lssl -lcrypto -lz -I/usr/local/include

* NOTE, we leak strdup calls, but they are 1 time leaks, and this process
* will simply exec another process, so we ignore them.
*/

#include <errno.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define shebangNAME "script_gcc"
#define CC "gcc"
#define CC_OPTS "-lm -x c"
#define IEXT ".c"
#define OEXT ".out"

/* return time of file modification. If file does not exit, return 0
* so that if we compare, it will always be older, and will be built */
time_t modtime(const char *filename) {
struct stat st;

if(stat(filename, &st) != EXIT_SUCCESS)
return 0;
return st.st_mtime;
}
/* join a pair of strings */
char *sjoin(const char *s1, const char *s2){
char buf[BUFSIZ];

if (!s1) s1=""; if (!s2) s2="";
snprintf(buf, sizeof(buf), "%s%s", s1, s2);
return strdup(buf);
}
/* compiles the original script file. It skips the first line (the shebang)
* and compiles using "gcc .... -x c -" which avoids having to write a temp
* source file (minus the shebang), we instead pipe source to gcc */
int compile(const char *srcpath, const char *binpath, const char *ex_comp_opts) {
char buf[BUFSIZ];
FILE *fsrc, *fcmp;

sprintf(buf, "%s %s %s -o %s -", CC, CC_OPTS, ex_comp_opts, binpath);
fsrc=fopen(srcpath, "r");
if (!fsrc) return -1;
fcmp=popen(buf, "w"); /* open up our gcc pipe to send it source */
if (!fcmp) { fclose(fsrc); return -1; }

/* skip shebang line, then compile rest of the file. */
fgets(buf, sizeof(buf), fsrc);
fgets(buf, sizeof(buf), fsrc);
while (!feof(fsrc)) {
fputs(buf, fcmp); /* compile this line of source with gcc */
fgets(buf, sizeof(buf), fsrc);
}
fclose(fsrc);
return pclose(fcmp);
}

/* tries to open the file 'argv0'. If we can open that file we read first line
* and make SURE it is a script_gcc file. If it is a script_gcc file, we
* look for any extra params for the compiler (params to the shebang line)
* and if we find them, they are returned in ex_comp_opts.
* return 0 if this is NOT a shebang file, return 1 if it IS the shebang file.
*/
int load_shebangline(const char *argv0, char **ex_comp_opts) {
char opt[BUFSIZ], *cp;
FILE *in = fopen(argv0, "r");

if (!in) return 0;
fgets(opt, sizeof(opt), in);
fclose(in);
/* ok, we found a readable file, but IS it our shebang file? */
strtok(opt, "\r\n");
if (strncmp(opt, "#!", 2) || !strstr(opt, shebangNAME))
return 0; /* nope, keep looking */
cp = strstr(opt, shebangNAME)+strlen(shebangNAME);
if (*cp) /* capture compiler extra params, if any */
*ex_comp_opts = strdup(cp);
return 1;
}

/* NOTE, the argv[] array is different than 'normal' C programs. argv[0] is
* the shebang exe file. then argv[1] ... argv[p] are the params that follow
* the shebang script name (from line1 of the script file). NOTE, some systems
* (Linux, cygwin), will pack all of these options into argv[1] with spaces.
* There is NO 'standard' on exactly what the layout it of these shebang params
* may be, we only know that there will be 0 or more params BEFORE the script
* name (which is the argv[0] we normally 'expect). Then argv[p+1] is the name
* of script being run. NOTE if there are no shebang args, argv[1] will be
* the script file name. The script file name is our expected argv[0], and
* all the params following that are the normal argv[1]...argv[n] we expect.
*/
int main(int argc, char *const argv[]) {
int i;
char exec_path[BUFSIZ], *argv0_basename, *ex_comp_opts=0, **dir,
*paths[] = {
NULL, /* paths[0] will be filled in later, to dir of the script */
"/usr/local/bin",
sjoin(getenv("HOME"), "/bin"),
sjoin(getenv("HOME"), "/tmp"),
getenv("HOME"),
sjoin(getenv("HOME"), "/Desktop"),
/* . and /tmp removed due to security concerns
".",
"/tmp", */
NULL
};

/* parse args, looking for the one that is the script. This would have been argv[0] if not exec'd as a shebang */
for (i = 1; i < argc; ++i) {
if (load_shebangline(argv[1], &ex_comp_opts)) {
argc -= i;
i = 0;
break;
}
++argv;
}
if (i)
return !fprintf(stderr, "could not locate proper %s shebang file!!\n", shebangNAME) | ENOENT;
++argv;
/* found it. Now argv[0] is the 'script' name, and rest of argv[] is params to the script */
argv0_basename = basename(strdup(argv[0]));
paths[0] = dirname(strdup(argv[0]));
/* find a cached version of the script, and if we find it, run it */
for(dir = paths; *dir; dir++) {
snprintf(exec_path, sizeof(exec_path), "%s/%s%s", *dir, argv0_basename, OEXT);
if(modtime(exec_path) >= modtime(argv[0]))
execvp(exec_path, argv); /* found a newer cached compiled file. Run it */
}
/* no cached file, or script is newer. So find a writeable dir */
for(dir = paths; *dir; dir++) {
if(!access(*dir, W_OK))
break;
}
if (!*dir)
return !fprintf(stderr, "No writeable directory for compile of the script %s\n", argv[0]) | EACCES;
/* compile and exec the result from our C script source file */
snprintf(exec_path, sizeof(exec_path), "%s/%s%s", *dir, argv0_basename, OEXT);
if(!compile(argv[0], exec_path, ex_comp_opts))
execvp(exec_path, argv);
return !fprintf(stderr, "%s : executable not available\n", exec_path) | ENOENT;
}
</lang>

'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc
/*
* note, any additional libs or include paths would have params added after
* the script_gcc parts of the shebang line, such as:
* #!/usr/local/bin/script_gcc -lgmp -I/usr/local/include/gmp5
*/
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>