/* RCS $Id: sysintf.c,v 1.7 2006-04-20 12:02:41 hr Exp $ -- -- SYNOPSIS -- System independent interface -- -- DESCRIPTION -- These are the routines constituting the system interface. -- The system is taken to be essentially POSIX conformant. -- The original code was extensively revised by T J Thompson at MKS, -- and the library cacheing was added by Eric Gisin at MKS. I then -- revised the code yet again, to improve the lib cacheing, and to -- make it more portable. -- -- The following is a list of routines that are required by this file -- in order to work. These routines are provided as functions by the -- standard C lib of the target system or as #defines in system/sysintf.h -- or via appropriate C code in the system/ directory for the given -- system. -- -- The first group must be provided by a file in the system/ directory -- the second group is ideally provided by the C lib. However, there -- are instances where the C lib implementation of the specified routine -- does not exist, or is incorrect. In these instances the routine -- must be provided by the the user in the system/ directory of dmake. -- (For example, the bsd/ dir contains code for putenv(), and tempnam()) -- -- DMAKE SPECIFIC: -- seek_arch() -- touch_arch() -- void_lcache() -- runargv() -- DMSTAT() -- Remove_prq() -- -- C-LIB SPECIFIC: (should be present in your C-lib) -- utime() -- time() -- getenv() -- putenv() -- getcwd() -- signal() -- chdir() -- tempnam() -- -- AUTHOR -- Dennis Vadura, dvadura@dmake.wticorp.com -- -- WWW -- http://dmake.wticorp.com/ -- -- COPYRIGHT -- Copyright (c) 1996,1997 by WTI Corp. All rights reserved. -- -- This program is NOT free software; you can redistribute it and/or -- modify it under the terms of the Software License Agreement Provided -- in the file /readme/license.txt. -- -- LOG -- Use cvs log to obtain detailed change logs. */ /* The following two include files are only needed for GetModuleFileName() * * therefore they are not included in extern.h. Unfortunately they cannot * * be loaded after extern.h because extern.h defines PVOID and this leads * * to conflicts when including windows.h. */ #if defined(_MSC_VER) || defined(__MINGW32__) # define HAVE_GETMODULEFILENAMEFUNC 1 # include # include /* this is needed for the _ftime call below. Only needed here. */ # include #endif #include "extern.h" #include "sysintf.h" /* ** Tries to stat the file name. Returns 0 if the file ** does not exist. Note that if lib is not null it tries to stat ** the name found inside lib. ** ** If member is NOT nil then look for the library object which defines the ** symbol given by name. If found DmStrDup the name and return make the ** pointer pointed at by sym point at it. Not handled for now! */ static time_t really_dostat(name, buf) char *name; struct stat *buf; { return( ( DMSTAT(name,buf)==-1 || (STOBOOL(Augmake) && (buf->st_mode & S_IFDIR))) ? (time_t)0L : (time_t) buf->st_mtime ); } PUBLIC time_t Do_stat(name, lib, member, force) char *name; char *lib; char **member; int force; { struct stat buf; time_t seek_arch(); if( member != NIL(char *) ) Fatal("Library symbol names not supported"); buf.st_mtime = (time_t)0L; if( lib != NIL(char) ) return( seek_arch(Basename(name), lib) ); else if( strlen(Basename(name)) > NameMax ) { Warning( "Filename [%s] longer than value of NAMEMAX [%d].\n\ Assume unix time 0.\n", Basename(name), NameMax ); return((time_t)0L); } else if( STOBOOL(UseDirCache) ) return(CacheStat(name,force)); else return(really_dostat(name,&buf)); } /* Touch existing file to force modify time to present. */ PUBLIC int Do_touch(name, lib, member) char *name; char *lib; char **member; { if( member != NIL(char *) ) Fatal("Library symbol names not supported"); if (lib != NIL(char)) return( touch_arch(Basename(name), lib) ); else if( strlen(Basename(name)) > NameMax ) { Warning( "Filename [%s] longer than value of NAMEMAX [%d].\n\ File timestamp not updated to present time.\n", Basename(name), NameMax ); return(-1); } else #ifdef HAVE_UTIME_NULL return( utime(name, NULL) ); #else # error "Utime NULL not supported" #endif } PUBLIC void Void_lib_cache( lib_name, member_name )/* ========================================= Void the library cache for lib lib_name, and member member_name. */ char *lib_name; char *member_name; { VOID_LCACHE( lib_name, member_name ); } /* ** return the current time */ PUBLIC time_t Do_time() { return (time( NIL(time_t) )); } /* ** Print profiling information */ PUBLIC void Do_profile_output( text, mtype, target ) char *text; uint16 mtype; CELLPTR target; { time_t time_sec; uint32 time_msec; char *tstrg; char *tname; #ifdef HAVE_GETTIMEOFDAY struct timeval timebuffer; gettimeofday(&timebuffer, NULL); time_sec = timebuffer.tv_sec; time_msec = timebuffer.tv_usec/1000; #else #if defined(_MSC_VER) || defined(__MINGW32__) struct _timeb timebuffer; _ftime( &timebuffer ); time_sec = timebuffer.time; time_msec = timebuffer.millitm; # else time_sec = time( NIL(time_t) ); time_msec = 0; # endif #endif tname = target->CE_NAME; if( mtype & M_TARGET ) { tstrg = "target"; /* Don't print special targets .TARGETS and .ROOT */ if( tname[0] == '.' && (strcmp(".TARGETS", tname) == 0 || \ strcmp(".ROOT", tname) == 0) ) { return; } } else { tstrg = "recipe"; } /* Don't print shell escape targets if not especially requested. */ if( (target->ce_attr & A_SHELLESC) && !(Measure & M_SHELLESC) ) { return; } /* Print absolute path if requested. */ if( !(target->ce_attr & A_SHELLESC) && (Measure & M_ABSPATH) ) { printf("%s %s %lu.%.3u %s%s%s\n",text, tstrg, time_sec, time_msec, Pwd, DirSepStr, tname); } else { printf("%s %s %lu.%.3u %s\n",text, tstrg, time_sec, time_msec, tname); } } /* ** Execute the string passed in as a command and return ** the return code. The command line arguments are ** assumed to be separated by spaces or tabs. The first ** such argument is assumed to be the command. ** ** If group is true then this is a group of commands to be fed to the ** the shell as a single unit. In this case cmd is of the form ** "file" indicating the file that should be read by the shell ** in order to execute the command group. */ PUBLIC int Do_cmnd(cmd, group, do_it, target, ignore, shell, last) char *cmd; int group; int do_it; CELLPTR target; int ignore; int shell; int last; { int i; if( !do_it ) { if( last && !Doing_bang ) { Update_time_stamp( target ); } return(0); } if ( target->ce_attr & A_ERROR ) { if ( last ) { Update_time_stamp( target ); } return(0); } if( Max_proc == 1 ) Wait_for_completion = TRUE; if( (i = runargv(target, ignore, group, last, shell, cmd)) == -1 ) /* Only fails for failed spawn. (Spawn is disabled ATM.) */ Quit(0); /* NOTE: runargv must return either 0 or 1, 0 ==> command executed, and * we waited for it to return, 1 ==> command started and is running * concurrently with make process or -1 if something failed. */ /* NOTE2: runargv currently always returns 1 (possible speed up) or -1. */ return(i); } #define MINARGV 64 /* Take a command and pack it into an argument vector to be executed. */ PUBLIC char ** Pack_argv( group, shell, cmd ) int group; int shell; char *cmd; { static char **av = NIL(char *); static int avs = 0; int i = 0; if( av == NIL(char *) ) { TALLOC(av, MINARGV, char*); avs = MINARGV; } av[0] = NIL(char); if (*cmd) { Packed_shell = shell||group||(*DmStrPbrk(cmd, Shell_metas)!='\0'); if( Packed_shell ){ char* sh = group ? GShell : Shell; if( sh != NIL(char) ) { av[i++] = sh; if( (av[i] = (group?GShell_flags:Shell_flags)) != NIL(char) ) i++; av[i++] = cmd; av[i] = NIL(char); } else Fatal("%sSHELL macro not defined", group?"GROUP":""); } else { do { while( iswhite(*cmd) ) ++cmd; if( *cmd ) av[i++] = cmd; while( *cmd != '\0' && !iswhite(*cmd) ) ++cmd; if( *cmd ) *cmd++ = '\0'; if( i == avs ) { avs += MINARGV; av = (char **) realloc( av, avs*sizeof(char *) ); } } while( *cmd ); av[i] = NIL(char); } } return(av); } /* ** Return the value of ename from the environment ** if ename is not defined in the environment then ** NIL(char) should be returned */ PUBLIC char * Read_env_string(ename) char *ename; { return( getenv(ename) ); } /* ** Set the value of the environment string ename to value. ** Returns 0 if success, non-zero if failure */ PUBLIC int Write_env_string(ename, value) char *ename; char *value; { char* p; char* envstr = DmStrAdd(ename, value, FALSE); p = envstr+strlen(ename); /* Don't change this code, DmStrAdd does not */ *p++ = '='; /* add the space if *value is 0, it does */ if( !*value ) *p = '\0'; /* allocate enough memory for one though. */ return( putenv(envstr) ); } PUBLIC void ReadEnvironment() { extern char **Rule_tab; #if !defined(_MSC_VER) #if defined(__BORLANDC__) && __BORLANDC__ >= 0x500 extern char ** _RTLENTRY _EXPDATA environ; #else extern char **environ; #endif #endif char **rsave; #if !defined(__ZTC__) && !defined(_MPW) # define make_env() # define free_env() #else void make_env(); void free_env(); #endif make_env(); rsave = Rule_tab; Rule_tab = environ; Readenv = TRUE; Parse( NIL(FILE) ); Readenv = FALSE; Rule_tab = rsave; free_env(); } /* ** All we have to catch is SIGINT */ PUBLIC void Catch_signals(fn) void (*fn)(int); { /* FIXME: Check this and add error handling. */ if( (void (*)(int)) signal(SIGINT, SIG_IGN) != (void (*)(int))SIG_IGN ) signal( SIGINT, fn ); if( (void (*)(int)) signal(SIGQUIT, SIG_IGN) != (void (*)(int))SIG_IGN ) signal( SIGQUIT, fn ); } /* ** Clear any previously set signals */ PUBLIC void Clear_signals() { if( (void (*)())signal(SIGINT, SIG_IGN) != (void (*)())SIG_IGN ) signal( SIGINT, SIG_DFL ); if( (void (*)())signal(SIGQUIT, SIG_IGN) != (void (*)())SIG_IGN ) signal( SIGQUIT, SIG_DFL ); } /* ** Set program name */ PUBLIC void Prolog(argc, argv) int argc; char* argv[]; { Pname = (argc == 0) ? DEF_MAKE_PNAME : argv[0]; /* Only some native Windows compilers provide this functionality. */ #ifdef HAVE_GETMODULEFILENAMEFUNC if( (AbsPname = MALLOC( PATH_MAX, char)) == NIL(char) ) No_ram(); GetModuleFileName(NULL, AbsPname, PATH_MAX*sizeof(char)); #else AbsPname = ""; #endif Root = Def_cell( ".ROOT" ); Targets = Def_cell( ".TARGETS" ); Add_prerequisite(Root, Targets, FALSE, FALSE); Targets->ce_flag = Root->ce_flag = F_RULES|F_TARGET|F_STAT; Targets->ce_attr = Root->ce_attr = A_NOSTATE|A_PHONY; Root->ce_flag |= F_MAGIC; Root->ce_attr |= A_SEQ; tzset(); } /* ** Do any clean up for exit. */ PUBLIC void Epilog(ret_code) int ret_code; { Write_state(); Unlink_temp_files(Root); Hook_std_writes(NIL(char)); /* For MSDOS tee (-F option) */ exit( ret_code ); } /* ** Use the built-in functions of the operating system to get the current ** working directory. */ PUBLIC char * Get_current_dir() { static char buf[PATH_MAX+2]; return(getcwd(buf, sizeof(buf))); } /* ** change working directory */ PUBLIC int Set_dir(path) char* path; { return( chdir(path) ); } /* ** return switch char */ PUBLIC char Get_switch_char() { return( getswitchar() ); } /* ** Create a temporary file and open with exclusive access ** Path is updated with the filename and the file descriptor ** is returned. Note that the new name should be freed when ** the file is removed. **/ int Create_temp(tmpdir, path, suff) char *tmpdir; char **path; char *suff; { int fd; #if defined(HAVE_MKSTEMP) mode_t mask; *path = DmStrJoin( tmpdir, "/", -1, FALSE); *path = DmStrJoin( *path, "mkXXXXXX", -1, TRUE ); *path = DmStrJoin( *path, suff, -1, TRUE ); mask = umask(0044); fd = mkstemp( *path ); umask(mask); #elif defined(HAVE_TEMPNAM) char pidbuff[32]; #if _MSC_VER >= 1300 /* Create more unique filename for .NET2003 and newer. */ long npid; long nticks; npid = _getpid(); nticks = GetTickCount() & 0xfff; sprintf(pidbuff,"mk%d_%d_",npid,nticks); #else sprintf(pidbuff,"mk"); #endif *path = DmStrJoin( tempnam(tmpdir, pidbuff), suff, -1, TRUE ); fd = open(*path, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600); #else #error mkstemp() or tempnam() is needed #endif return fd; } /* ** Generate a temporary file name and open the file for writing. ** If a name cannot be generated or the file cannot be opened ** return -1, else return the fileno of the open file. ** and update the source file pointer to point at the new file name. ** Note that the new name should be freed when the file is removed. ** The file stream is opened with the given mode */ PUBLIC FILE* Get_temp(path, suff, mode) char **path; char *suff; char *mode; { int fd; FILE *fp; char *tmpdir; int tries = 20; tmpdir = Read_env_string( "TMPDIR" ); if( tmpdir == NIL(char) ) tmpdir = "/tmp"; while( --tries ) { if( (fd = Create_temp(tmpdir, path, suff)) != -1) break; free(*path); } if( fd != -1) { Def_macro( "TMPFILE", *path, M_MULTI|M_EXPANDED ); fp = fdopen(fd, mode); } else fp = NIL(FILE); return fp; } /* ** Open a new temporary file and set it up for writing. */ PUBLIC FILE * Start_temp( suffix, cp, fname ) char *suffix; CELLPTR cp; char **fname; { FILE *fp, *fp2; char *tmpname, *tmpname2; char *name; name = (cp != NIL(CELL))?cp->CE_NAME:"makefile text"; if( (fp = Get_temp(&tmpname, suffix, "w")) == NIL(FILE) ) Open_temp_error( tmpname, name ); Link_temp( cp, fp, tmpname ); *fname = tmpname; /* * link tempfiles generated by tempnam() to make sure that * they will be removed */ if ( *suffix != '\0' ) { tmpname2 = MALLOC( strlen( tmpname ) - strlen( suffix ) +1, char ); *tmpname2 = '\0'; strncat( tmpname2, tmpname, strlen( tmpname ) - strlen( suffix )); if( (fp2 = fopen(tmpname2, "w" )) == NIL(FILE) ) Open_temp_error( tmpname2, name ); Link_temp( cp, fp2, tmpname2 ); } return( fp ); } /* ** Issue an error on failing to open a temporary file */ PUBLIC void Open_temp_error( tmpname, name ) char *tmpname; char *name; { Fatal("Cannot open temp file `%s' while processing `%s'", tmpname, name ); } /* ** Link a temp file onto the list of files. */ PUBLIC void Link_temp( cp, fp, fname ) CELLPTR cp; FILE *fp; char *fname; { FILELISTPTR new; if( cp == NIL(CELL) ) cp = Root; TALLOC( new, 1, FILELIST ); new->fl_next = cp->ce_files; new->fl_name = fname; new->fl_file = fp; /* indicates temp file is open */ cp->ce_files = new; } /* ** Close a previously used temporary file. */ PUBLIC void Close_temp(cp, file) CELLPTR cp; FILE *file; { FILELISTPTR fl; if( cp == NIL(CELL) ) cp = Root; for( fl=cp->ce_files; fl && fl->fl_file != file; fl=fl->fl_next ); if( fl ) { fl->fl_file = NIL(FILE); fclose(file); } } /* ** Clean-up, and close all temporary files associated with a target. */ PUBLIC void Unlink_temp_files( cp )/* ========================== Unlink the tempfiles if any exist. Make sure you close the files first though. This ensures that under DOS there is no disk space lost. */ CELLPTR cp; { FILELISTPTR cur, next; if( cp == NIL(CELL) || cp->ce_files == NIL(FILELIST) ) return; for( cur=cp->ce_files; cur != NIL(FILELIST); cur=next ) { next = cur->fl_next; if( cur->fl_file ) fclose( cur->fl_file ); if( Verbose & V_LEAVE_TMP ) fprintf( stderr, "%s: Left temp file [%s]\n", Pname, cur->fl_name ); else (void) Remove_file( cur->fl_name ); FREE(cur->fl_name); FREE(cur); } cp->ce_files = NIL(FILELIST); } PUBLIC void Handle_result(status, ignore, abort_flg, target) int status; int ignore; int abort_flg; CELLPTR target; { status = ((status&0xff)==0 ? status>>8 : (status & 0xff)==SIGTERM ? -1 : (status & 0x7f)+128); if( status ) { if( !abort_flg ) { char buf[512]; sprintf(buf, "%s: Error code %d, while making '%s'", Pname, status, target->ce_fname ); if( ignore || Continue ) { if (!(Glob_attr & A_SILENT)) { strcat(buf, " (Ignored" ); if ( Continue ) { strcat(buf,",Continuing"); target->ce_attr |= A_ERROR; } strcat(buf,")"); if (Verbose) fprintf(stderr, "%s\n", buf); } if( target->ce_attr & A_ERRREMOVE && Remove_file( target->ce_fname ) == 0 && !(Glob_attr & A_SILENT)) fprintf(stderr,"%s: '%s' removed.\n", Pname, target->ce_fname); } else { fprintf(stderr, "%s\n",buf); if(!(target->ce_attr & A_PRECIOUS)||(target->ce_attr & A_ERRREMOVE)) if( Remove_file( target->ce_fname ) == 0 ) fprintf(stderr,"%s: '%s' removed.\n", Pname, target->ce_fname); Quit(0); } } else if(!(target->ce_attr & A_PRECIOUS)||(target->ce_attr & A_ERRREMOVE)) Remove_file( target->ce_fname ); } } PUBLIC void Update_time_stamp( cp )/* ========================= Update the time stamp of cp and scan the list of its prerequisites for files being marked as removable (ie. an inferred intermediate node). Remove them if there are any. */ CELLPTR cp; { HASHPTR hp; LINKPTR dp; CELLPTR tcp; time_t phonytime = (time_t)0L; int phony = ((cp->ce_attr&A_PHONY) != 0); /* Use the current time as phony timestamp for phony targets. */ /* This was changed with issue 34746. */ phonytime = Do_time(); for(dp=CeMeToo(cp); dp; dp=dp->cl_next) { tcp=dp->cl_prq; if( tcp->ce_attr & A_LIBRARY ) Void_lib_cache( tcp->ce_fname, NIL(char) ); else if( !Touch && (tcp->ce_attr & A_LIBRARYM) ) Void_lib_cache( tcp->ce_lib, tcp->ce_fname ); if( phony ) { tcp->ce_time = phonytime; } else if (Trace) { tcp->ce_time = Do_time(); } else { Stat_target(tcp, -1, TRUE); if( tcp->ce_time == (time_t) 0L ) tcp->ce_time = Do_time(); } if( Trace ) { tcp->ce_flag |= F_STAT; /* pretend we stated ok */ } if( Verbose & V_MAKE ) printf( "%s: <<<< Set [%s] time stamp to %lu\n", Pname, tcp->CE_NAME, tcp->ce_time ); if( Measure & M_TARGET ) Do_profile_output( "e", M_TARGET, tcp ); Unlink_temp_files( tcp ); tcp->ce_flag |= F_MADE; tcp->ce_attr |= A_UPDATED; } /* Scan the list of prerequisites and if we find one that is * marked as being removable, (ie. an inferred intermediate node) * then remove it. We remove a prerequisite by running the recipe * associated with the special target .REMOVE, with $< set to * the list of prerequisites to remove. */ /* Make sure we don't try to remove prerequisites for the .REMOVE * target. */ if( strcmp(cp->CE_NAME,".REMOVE") != 0 && (hp = Get_name(".REMOVE", Defs, FALSE)) != NIL(HASH) ) { register LINKPTR dp; int flag = FALSE; int rem; t_attr attr; tcp = hp->CP_OWNR; tcp->ce_flag |= F_TARGET; Clear_prerequisites( tcp ); for(dp=cp->ce_prq; dp != NIL(LINK); dp=dp->cl_next) { register CELLPTR prq = dp->cl_prq; attr = Glob_attr | prq->ce_attr; /* We seem to have problems here that F_MULTI subtargets get removed * that even though they are still needed because the A_PRECIOUS * was not propagated correctly. Solution: Don't remove subtargets, the * master target will be removed if is not needed. */ rem = (prq->ce_flag & F_REMOVE) && (prq->ce_flag & F_MADE ) && !(prq->ce_count ) && /* Don't remove F_MULTI subtargets. */ !(prq->ce_attr & A_PHONY) && !(attr & A_PRECIOUS); /* remove if rem is != 0 */ if(rem) { LINKPTR tdp; for(tdp=CeMeToo(prq); tdp; tdp=tdp->cl_next) { CELLPTR tmpcell=tdp->cl_prq; (Add_prerequisite(tcp,tmpcell,FALSE,FALSE))->cl_flag|=F_TARGET; tmpcell->ce_flag &= ~F_REMOVE; } flag = TRUE; } } if( flag ) { int sv_force = Force; Force = FALSE; Remove_prq( tcp ); Force = sv_force; for(dp=tcp->ce_prq; dp != NIL(LINK); dp=dp->cl_next) { register CELLPTR prq = dp->cl_prq; prq->ce_flag &= ~(F_MADE|F_VISITED|F_STAT); prq->ce_flag |= F_REMOVE; prq->ce_time = (time_t)0L; } } } } PUBLIC int Remove_file( name ) char *name; { struct stat buf; if( stat(name, &buf) != 0 ) return 1; if( (buf.st_mode & S_IFMT) == S_IFDIR ) return 1; return(unlink(name)); }