2013-06-25 04:20:54 +04:00
|
|
|
/* $NetBSD: meta.c,v 1.32 2013/06/25 00:20:54 sjg Exp $ */
|
2011-03-31 02:03:49 +04:00
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* Implement 'meta' mode.
|
|
|
|
* Adapted from John Birrell's patches to FreeBSD make.
|
|
|
|
* --sjg
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2009-2010, Juniper Networks, Inc.
|
|
|
|
* Portions Copyright (c) 2009, John Birrell.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
2010-09-14 01:31:59 +04:00
|
|
|
#if defined(USE_META)
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <libgen.h>
|
|
|
|
#include <errno.h>
|
2010-09-14 00:34:21 +04:00
|
|
|
#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
|
2010-09-13 19:36:57 +04:00
|
|
|
#include <err.h>
|
2010-09-14 00:34:21 +04:00
|
|
|
#endif
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
#include "make.h"
|
|
|
|
#include "job.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_FILEMON_H
|
|
|
|
# include <filemon.h>
|
|
|
|
#endif
|
|
|
|
#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
|
|
|
|
# define USE_FILEMON
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static BuildMon Mybm; /* for compat */
|
2013-06-25 04:20:54 +04:00
|
|
|
static Lst metaBailiwick; /* our scope of control */
|
|
|
|
static Lst metaIgnorePaths; /* paths we deliberately ignore */
|
|
|
|
|
|
|
|
#ifndef MAKE_META_IGNORE_PATHS
|
|
|
|
#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS"
|
|
|
|
#endif
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
Boolean useMeta = FALSE;
|
|
|
|
static Boolean useFilemon = FALSE;
|
|
|
|
static Boolean writeMeta = FALSE;
|
|
|
|
static Boolean metaEnv = FALSE; /* don't save env unless asked */
|
|
|
|
static Boolean metaVerbose = FALSE;
|
|
|
|
static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */
|
2011-03-27 23:39:21 +04:00
|
|
|
static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
|
2011-08-28 07:54:07 +04:00
|
|
|
static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
extern Boolean forceJobs;
|
|
|
|
extern Boolean comatMake;
|
2012-06-27 21:22:58 +04:00
|
|
|
extern char **environ;
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
|
|
|
|
|
|
|
|
#ifndef N2U
|
|
|
|
# define N2U(n, u) (((n) + ((u) - 1)) / (u))
|
|
|
|
#endif
|
|
|
|
#ifndef ROUNDUP
|
|
|
|
# define ROUNDUP(n, u) (N2U((n), (u)) * (u))
|
|
|
|
#endif
|
|
|
|
|
2010-09-14 00:34:21 +04:00
|
|
|
#if !defined(HAVE_STRSEP)
|
|
|
|
# define strsep(s, d) stresep((s), (d), 0)
|
|
|
|
#endif
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* Filemon is a kernel module which snoops certain syscalls.
|
|
|
|
*
|
|
|
|
* C chdir
|
|
|
|
* E exec
|
|
|
|
* F [v]fork
|
|
|
|
* L [sym]link
|
|
|
|
* M rename
|
|
|
|
* R read
|
|
|
|
* W write
|
|
|
|
* S stat
|
|
|
|
*
|
|
|
|
* See meta_oodate below - we mainly care about 'E' and 'R'.
|
|
|
|
*
|
|
|
|
* We can still use meta mode without filemon, but
|
|
|
|
* the benefits are more limited.
|
|
|
|
*/
|
|
|
|
#ifdef USE_FILEMON
|
|
|
|
# ifndef _PATH_FILEMON
|
|
|
|
# define _PATH_FILEMON "/dev/filemon"
|
|
|
|
# endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open the filemon device.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
filemon_open(BuildMon *pbm)
|
|
|
|
{
|
|
|
|
int retry;
|
|
|
|
|
|
|
|
pbm->mon_fd = pbm->filemon_fd = -1;
|
|
|
|
if (!useFilemon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (retry = 5; retry >= 0; retry--) {
|
|
|
|
if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pbm->filemon_fd < 0) {
|
|
|
|
useFilemon = FALSE;
|
|
|
|
warn("Could not open %s", _PATH_FILEMON);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We use a file outside of '.'
|
|
|
|
* to avoid a FreeBSD kernel bug where unlink invalidates
|
|
|
|
* cwd causing getcwd to do a lot more work.
|
|
|
|
* We only care about the descriptor.
|
|
|
|
*/
|
|
|
|
pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
|
|
|
|
if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
|
|
|
|
err(1, "Could not set filemon file descriptor!");
|
|
|
|
}
|
|
|
|
/* we don't need these once we exec */
|
|
|
|
(void)fcntl(pbm->mon_fd, F_SETFD, 1);
|
|
|
|
(void)fcntl(pbm->filemon_fd, F_SETFD, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the build monitor output file and write records to the target's
|
|
|
|
* metadata file.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
filemon_read(FILE *mfp, int fd)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
|
|
|
|
/* Check if we're not writing to a meta data file.*/
|
|
|
|
if (mfp == NULL) {
|
|
|
|
if (fd >= 0)
|
|
|
|
close(fd); /* not interested */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* rewind */
|
2011-09-21 18:30:47 +04:00
|
|
|
(void)lseek(fd, (off_t)0, SEEK_SET);
|
2010-09-13 19:36:57 +04:00
|
|
|
if ((fp = fdopen(fd, "r")) == NULL)
|
|
|
|
err(1, "Could not read build monitor file '%d'", fd);
|
|
|
|
|
|
|
|
fprintf(mfp, "-- filemon acquired metadata --\n");
|
|
|
|
|
|
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
|
|
fprintf(mfp, "%s", buf);
|
|
|
|
}
|
|
|
|
fflush(mfp);
|
|
|
|
clearerr(fp);
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-02-14 00:22:41 +03:00
|
|
|
/*
|
|
|
|
* when realpath() fails,
|
|
|
|
* we use this, to clean up ./ and ../
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
eat_dots(char *buf, size_t bufsz, int dots)
|
|
|
|
{
|
|
|
|
char *cp;
|
|
|
|
char *cp2;
|
|
|
|
const char *eat;
|
|
|
|
size_t eatlen;
|
|
|
|
|
|
|
|
switch (dots) {
|
|
|
|
case 1:
|
|
|
|
eat = "/./";
|
|
|
|
eatlen = 2;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
eat = "/../";
|
|
|
|
eatlen = 3;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
cp = strstr(buf, eat);
|
|
|
|
if (cp) {
|
|
|
|
cp2 = cp + eatlen;
|
|
|
|
if (dots == 2 && cp > buf) {
|
|
|
|
do {
|
|
|
|
cp--;
|
|
|
|
} while (cp > buf && *cp != '/');
|
|
|
|
}
|
|
|
|
if (*cp == '/') {
|
|
|
|
strlcpy(cp, cp2, bufsz - (cp - buf));
|
|
|
|
} else {
|
|
|
|
return; /* can't happen? */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (cp);
|
|
|
|
}
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
static char *
|
|
|
|
meta_name(struct GNode *gn, char *mname, size_t mnamelen,
|
|
|
|
const char *dname,
|
|
|
|
const char *tname)
|
|
|
|
{
|
|
|
|
char buf[MAXPATHLEN];
|
|
|
|
char cwd[MAXPATHLEN];
|
|
|
|
char *rp;
|
|
|
|
char *cp;
|
|
|
|
char *tp;
|
|
|
|
char *p[4]; /* >= number of possible uses */
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
if (!dname)
|
|
|
|
dname = Var_Value(".OBJDIR", gn, &p[i++]);
|
|
|
|
if (!tname)
|
|
|
|
tname = Var_Value(TARGET, gn, &p[i++]);
|
|
|
|
|
2011-02-14 00:22:41 +03:00
|
|
|
if (realpath(dname, cwd))
|
|
|
|
dname = cwd;
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* Weed out relative paths from the target file name.
|
|
|
|
* We have to be careful though since if target is a
|
|
|
|
* symlink, the result will be unstable.
|
|
|
|
* So we use realpath() just to get the dirname, and leave the
|
|
|
|
* basename as given to us.
|
|
|
|
*/
|
|
|
|
if ((cp = strrchr(tname, '/'))) {
|
|
|
|
if (realpath(tname, buf)) {
|
|
|
|
if ((rp = strrchr(buf, '/'))) {
|
|
|
|
rp++;
|
|
|
|
cp++;
|
|
|
|
if (strcmp(cp, rp) != 0)
|
|
|
|
strlcpy(rp, cp, sizeof(buf) - (rp - buf));
|
|
|
|
}
|
|
|
|
tname = buf;
|
2011-02-14 00:22:41 +03:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* We likely have a directory which is about to be made.
|
|
|
|
* We pretend realpath() succeeded, to have a chance
|
|
|
|
* of generating the same meta file name that we will
|
|
|
|
* next time through.
|
|
|
|
*/
|
|
|
|
if (tname[0] == '/') {
|
|
|
|
strlcpy(buf, tname, sizeof(buf));
|
|
|
|
} else {
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
|
|
|
|
}
|
|
|
|
eat_dots(buf, sizeof(buf), 1); /* ./ */
|
|
|
|
eat_dots(buf, sizeof(buf), 2); /* ../ */
|
|
|
|
tname = buf;
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* on some systems dirname may modify its arg */
|
|
|
|
tp = bmake_strdup(tname);
|
|
|
|
if (strcmp(dname, dirname(tp)) == 0)
|
|
|
|
snprintf(mname, mnamelen, "%s.meta", tname);
|
|
|
|
else {
|
|
|
|
snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Replace path separators in the file name after the
|
|
|
|
* current object directory path.
|
|
|
|
*/
|
|
|
|
cp = mname + strlen(dname) + 1;
|
|
|
|
|
|
|
|
while (*cp != '\0') {
|
|
|
|
if (*cp == '/')
|
|
|
|
*cp = '_';
|
|
|
|
cp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(tp);
|
|
|
|
for (i--; i >= 0; i--) {
|
|
|
|
if (p[i])
|
|
|
|
free(p[i]);
|
|
|
|
}
|
|
|
|
return (mname);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return true if running ${.MAKE}
|
|
|
|
* Bypassed if target is flagged .MAKE
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
is_submake(void *cmdp, void *gnp)
|
|
|
|
{
|
|
|
|
static char *p_make = NULL;
|
|
|
|
static int p_len;
|
|
|
|
char *cmd = cmdp;
|
|
|
|
GNode *gn = gnp;
|
|
|
|
char *mp = NULL;
|
|
|
|
char *cp;
|
|
|
|
char *cp2;
|
|
|
|
int rc = 0; /* keep looking */
|
|
|
|
|
|
|
|
if (!p_make) {
|
|
|
|
p_make = Var_Value(".MAKE", gn, &cp);
|
|
|
|
p_len = strlen(p_make);
|
|
|
|
}
|
|
|
|
cp = strchr(cmd, '$');
|
|
|
|
if ((cp)) {
|
|
|
|
mp = Var_Subst(NULL, cmd, gn, FALSE);
|
|
|
|
cmd = mp;
|
|
|
|
}
|
|
|
|
cp2 = strstr(cmd, p_make);
|
|
|
|
if ((cp2)) {
|
|
|
|
switch (cp2[p_len]) {
|
|
|
|
case '\0':
|
|
|
|
case ' ':
|
|
|
|
case '\t':
|
|
|
|
case '\n':
|
|
|
|
rc = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cp2 > cmd && rc > 0) {
|
|
|
|
switch (cp2[-1]) {
|
|
|
|
case ' ':
|
|
|
|
case '\t':
|
|
|
|
case '\n':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = 0; /* no match */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mp)
|
|
|
|
free(mp);
|
|
|
|
return (rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct meta_file_s {
|
|
|
|
FILE *fp;
|
|
|
|
GNode *gn;
|
|
|
|
} meta_file_t;
|
|
|
|
|
|
|
|
static int
|
|
|
|
printCMD(void *cmdp, void *mfpp)
|
|
|
|
{
|
|
|
|
meta_file_t *mfp = mfpp;
|
|
|
|
char *cmd = cmdp;
|
|
|
|
char *cp = NULL;
|
|
|
|
|
|
|
|
if (strchr(cmd, '$')) {
|
|
|
|
cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE);
|
|
|
|
}
|
|
|
|
fprintf(mfp->fp, "CMD %s\n", cmd);
|
|
|
|
if (cp)
|
|
|
|
free(cp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Certain node types never get a .meta file
|
|
|
|
*/
|
|
|
|
#define SKIP_META_TYPE(_type) do { \
|
|
|
|
if ((gn->type & __CONCAT(OP_, _type))) { \
|
|
|
|
if (DEBUG(META)) { \
|
|
|
|
fprintf(debug_file, "Skipping meta for %s: .%s\n", \
|
|
|
|
gn->name, __STRING(_type)); \
|
|
|
|
} \
|
|
|
|
return (NULL); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static FILE *
|
|
|
|
meta_create(BuildMon *pbm, GNode *gn)
|
|
|
|
{
|
|
|
|
meta_file_t mf;
|
|
|
|
char buf[MAXPATHLEN];
|
|
|
|
char objdir[MAXPATHLEN];
|
|
|
|
char **ptr;
|
|
|
|
const char *dname;
|
|
|
|
const char *tname;
|
|
|
|
char *fname;
|
|
|
|
const char *cp;
|
|
|
|
char *p[4]; /* >= possible uses */
|
|
|
|
int i;
|
|
|
|
struct stat fs;
|
|
|
|
|
|
|
|
|
|
|
|
/* This may be a phony node which we don't want meta data for... */
|
|
|
|
/* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
|
|
|
|
/* Or it may be explicitly flagged as .NOMETA */
|
|
|
|
SKIP_META_TYPE(NOMETA);
|
|
|
|
/* Unless it is explicitly flagged as .META */
|
|
|
|
if (!(gn->type & OP_META)) {
|
|
|
|
SKIP_META_TYPE(PHONY);
|
|
|
|
SKIP_META_TYPE(SPECIAL);
|
|
|
|
SKIP_META_TYPE(MAKE);
|
|
|
|
}
|
|
|
|
|
|
|
|
mf.fp = NULL;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
dname = Var_Value(".OBJDIR", gn, &p[i++]);
|
|
|
|
tname = Var_Value(TARGET, gn, &p[i++]);
|
|
|
|
|
|
|
|
/* The object directory may not exist. Check it.. */
|
|
|
|
if (stat(dname, &fs) != 0) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
|
|
|
|
gn->name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
/* Check if there are no commands to execute. */
|
|
|
|
if (Lst_IsEmpty(gn->commands)) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "Skipping meta for %s: no commands\n",
|
|
|
|
gn->name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make sure these are canonical */
|
|
|
|
if (realpath(dname, objdir))
|
|
|
|
dname = objdir;
|
|
|
|
|
|
|
|
/* If we aren't in the object directory, don't create a meta file. */
|
2011-03-27 23:39:21 +04:00
|
|
|
if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
|
2010-09-13 19:36:57 +04:00
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
|
|
|
|
gn->name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (!(gn->type & OP_META)) {
|
|
|
|
/* We do not generate .meta files for sub-makes */
|
|
|
|
if (Lst_ForEach(gn->commands, is_submake, gn)) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "Skipping meta for %s: .MAKE\n",
|
|
|
|
gn->name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (metaVerbose) {
|
|
|
|
char *mp;
|
|
|
|
|
|
|
|
/* Describe the target we are building */
|
|
|
|
mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0);
|
|
|
|
if (*mp)
|
|
|
|
fprintf(stdout, "%s\n", mp);
|
|
|
|
free(mp);
|
|
|
|
}
|
|
|
|
/* Get the basename of the target */
|
|
|
|
if ((cp = strrchr(tname, '/')) == NULL) {
|
|
|
|
cp = tname;
|
|
|
|
} else {
|
|
|
|
cp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
if (strcmp(cp, makeDependfile) == 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (!writeMeta)
|
|
|
|
/* Don't create meta data. */
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
|
|
|
|
dname, tname);
|
|
|
|
|
2011-02-14 00:22:41 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "meta_create: %s\n", fname);
|
|
|
|
#endif
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
if ((mf.fp = fopen(fname, "w")) == NULL)
|
|
|
|
err(1, "Could not open meta file '%s'", fname);
|
|
|
|
|
|
|
|
fprintf(mf.fp, "# Meta data file %s\n", fname);
|
|
|
|
|
|
|
|
mf.gn = gn;
|
|
|
|
|
|
|
|
Lst_ForEach(gn->commands, printCMD, &mf);
|
|
|
|
|
|
|
|
fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
|
|
|
|
fprintf(mf.fp, "TARGET %s\n", tname);
|
|
|
|
|
|
|
|
if (metaEnv) {
|
|
|
|
for (ptr = environ; *ptr != NULL; ptr++)
|
|
|
|
fprintf(mf.fp, "ENV %s\n", *ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(mf.fp, "-- command output --\n");
|
|
|
|
fflush(mf.fp);
|
|
|
|
|
|
|
|
Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
|
|
|
|
Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
|
2011-08-28 07:54:07 +04:00
|
|
|
|
|
|
|
gn->type |= OP_META; /* in case anyone wants to know */
|
|
|
|
if (metaSilent) {
|
|
|
|
gn->type |= OP_SILENT;
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
out:
|
|
|
|
for (i--; i >= 0; i--) {
|
|
|
|
if (p[i])
|
|
|
|
free(p[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (mf.fp);
|
|
|
|
}
|
|
|
|
|
2011-03-27 23:39:21 +04:00
|
|
|
static Boolean
|
|
|
|
boolValue(char *s)
|
|
|
|
{
|
|
|
|
switch(*s) {
|
|
|
|
case '0':
|
|
|
|
case 'N':
|
|
|
|
case 'n':
|
|
|
|
case 'F':
|
|
|
|
case 'f':
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
|
2013-03-23 09:31:29 +04:00
|
|
|
/*
|
|
|
|
* Initialization we need before reading makefiles.
|
|
|
|
*/
|
|
|
|
void
|
2013-05-17 01:56:56 +04:00
|
|
|
meta_init(void)
|
2013-03-23 09:31:29 +04:00
|
|
|
{
|
|
|
|
#ifdef USE_FILEMON
|
|
|
|
/* this allows makefiles to test if we have filemon support */
|
|
|
|
Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialization we need after reading makefiles.
|
|
|
|
*/
|
2010-09-13 19:36:57 +04:00
|
|
|
void
|
2013-03-23 09:31:29 +04:00
|
|
|
meta_mode_init(const char *make_mode)
|
2010-09-13 19:36:57 +04:00
|
|
|
{
|
|
|
|
static int once = 0;
|
2011-03-27 23:39:21 +04:00
|
|
|
char *cp;
|
2010-09-13 19:36:57 +04:00
|
|
|
|
|
|
|
useMeta = TRUE;
|
|
|
|
useFilemon = TRUE;
|
|
|
|
writeMeta = TRUE;
|
|
|
|
|
|
|
|
if (make_mode) {
|
|
|
|
if (strstr(make_mode, "env"))
|
|
|
|
metaEnv = TRUE;
|
|
|
|
if (strstr(make_mode, "verb"))
|
|
|
|
metaVerbose = TRUE;
|
|
|
|
if (strstr(make_mode, "read"))
|
|
|
|
writeMeta = FALSE;
|
|
|
|
if (strstr(make_mode, "nofilemon"))
|
|
|
|
useFilemon = FALSE;
|
2011-03-27 23:47:46 +04:00
|
|
|
if ((cp = strstr(make_mode, "curdirok="))) {
|
|
|
|
metaCurdirOk = boolValue(&cp[9]);
|
2011-03-27 23:39:21 +04:00
|
|
|
}
|
2011-08-28 07:54:07 +04:00
|
|
|
if ((cp = strstr(make_mode, "silent="))) {
|
|
|
|
metaSilent = boolValue(&cp[7]);
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
if (strstr(make_mode, "ignore-cmd"))
|
|
|
|
metaIgnoreCMDs = TRUE;
|
|
|
|
/* for backwards compatability */
|
|
|
|
Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0);
|
|
|
|
Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0);
|
|
|
|
}
|
|
|
|
if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
|
|
|
|
/*
|
|
|
|
* The default value for MAKE_META_PREFIX
|
|
|
|
* prints the absolute path of the target.
|
|
|
|
* This works be cause :H will generate '.' if there is no /
|
|
|
|
* and :tA will resolve that to cwd.
|
|
|
|
*/
|
|
|
|
Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
|
|
|
|
}
|
|
|
|
if (once)
|
|
|
|
return;
|
|
|
|
once = 1;
|
|
|
|
memset(&Mybm, 0, sizeof(Mybm));
|
2011-05-05 00:38:31 +04:00
|
|
|
/*
|
|
|
|
* We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
|
|
|
|
*/
|
|
|
|
metaBailiwick = Lst_Init(FALSE);
|
|
|
|
cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, 0);
|
|
|
|
if (cp) {
|
|
|
|
str2Lst_Append(metaBailiwick, cp, NULL);
|
|
|
|
}
|
2013-06-25 04:20:54 +04:00
|
|
|
/*
|
|
|
|
* We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
|
|
|
|
*/
|
|
|
|
metaIgnorePaths = Lst_Init(FALSE);
|
|
|
|
Var_Append(MAKE_META_IGNORE_PATHS,
|
|
|
|
"/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
|
|
|
|
cp = Var_Subst(NULL,
|
|
|
|
"${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 0);
|
|
|
|
if (cp) {
|
|
|
|
str2Lst_Append(metaIgnorePaths, cp, NULL);
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In each case below we allow for job==NULL
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
meta_job_start(Job *job, GNode *gn)
|
|
|
|
{
|
|
|
|
BuildMon *pbm;
|
|
|
|
|
|
|
|
if (job != NULL) {
|
|
|
|
pbm = &job->bm;
|
|
|
|
} else {
|
|
|
|
pbm = &Mybm;
|
|
|
|
}
|
|
|
|
pbm->mfp = meta_create(pbm, gn);
|
|
|
|
#ifdef USE_FILEMON_ONCE
|
|
|
|
/* compat mode we open the filemon dev once per command */
|
|
|
|
if (job == NULL)
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
#ifdef USE_FILEMON
|
|
|
|
if (pbm->mfp != NULL && useFilemon) {
|
|
|
|
filemon_open(pbm);
|
|
|
|
} else {
|
|
|
|
pbm->mon_fd = pbm->filemon_fd = -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The child calls this before doing anything.
|
|
|
|
* It does not disturb our state.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
meta_job_child(Job *job)
|
|
|
|
{
|
|
|
|
#ifdef USE_FILEMON
|
|
|
|
BuildMon *pbm;
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
if (job != NULL) {
|
|
|
|
pbm = &job->bm;
|
|
|
|
} else {
|
|
|
|
pbm = &Mybm;
|
|
|
|
}
|
|
|
|
pid = getpid();
|
|
|
|
if (pbm->mfp != NULL && useFilemon) {
|
|
|
|
if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
|
|
|
|
err(1, "Could not set filemon pid!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_job_error(Job *job, GNode *gn, int flags, int status)
|
|
|
|
{
|
|
|
|
char cwd[MAXPATHLEN];
|
|
|
|
BuildMon *pbm;
|
|
|
|
|
|
|
|
if (job != NULL) {
|
|
|
|
pbm = &job->bm;
|
|
|
|
} else {
|
|
|
|
if (!gn)
|
|
|
|
gn = job->node;
|
|
|
|
pbm = &Mybm;
|
|
|
|
}
|
|
|
|
if (pbm->mfp != NULL) {
|
|
|
|
fprintf(pbm->mfp, "*** Error code %d%s\n",
|
|
|
|
status,
|
|
|
|
(flags & JOB_IGNERR) ?
|
|
|
|
"(ignored)" : "");
|
|
|
|
}
|
|
|
|
if (gn) {
|
|
|
|
Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
|
|
|
|
}
|
|
|
|
getcwd(cwd, sizeof(cwd));
|
|
|
|
Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
|
2010-12-16 02:03:35 +03:00
|
|
|
if (pbm && pbm->meta_fname[0]) {
|
2010-09-13 19:36:57 +04:00
|
|
|
Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
|
|
|
|
}
|
2011-03-31 10:50:43 +04:00
|
|
|
meta_job_finish(job);
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_job_output(Job *job, char *cp, const char *nl)
|
|
|
|
{
|
|
|
|
BuildMon *pbm;
|
|
|
|
|
|
|
|
if (job != NULL) {
|
|
|
|
pbm = &job->bm;
|
|
|
|
} else {
|
|
|
|
pbm = &Mybm;
|
|
|
|
}
|
|
|
|
if (pbm->mfp != NULL) {
|
|
|
|
if (metaVerbose) {
|
|
|
|
static char *meta_prefix = NULL;
|
|
|
|
static int meta_prefix_len;
|
|
|
|
|
|
|
|
if (!meta_prefix) {
|
|
|
|
char *cp2;
|
|
|
|
|
|
|
|
meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0);
|
|
|
|
if ((cp2 = strchr(meta_prefix, '$')))
|
|
|
|
meta_prefix_len = cp2 - meta_prefix;
|
|
|
|
else
|
|
|
|
meta_prefix_len = strlen(meta_prefix);
|
|
|
|
}
|
|
|
|
if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
|
|
|
|
cp = strchr(cp+1, '\n');
|
|
|
|
if (!cp++)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fprintf(pbm->mfp, "%s%s", cp, nl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_cmd_finish(void *pbmp)
|
|
|
|
{
|
|
|
|
#ifdef USE_FILEMON
|
|
|
|
BuildMon *pbm = pbmp;
|
|
|
|
|
|
|
|
if (!pbm)
|
|
|
|
pbm = &Mybm;
|
|
|
|
|
|
|
|
if (pbm->filemon_fd >= 0) {
|
|
|
|
close(pbm->filemon_fd);
|
|
|
|
filemon_read(pbm->mfp, pbm->mon_fd);
|
|
|
|
pbm->filemon_fd = pbm->mon_fd = -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_job_finish(Job *job)
|
|
|
|
{
|
|
|
|
BuildMon *pbm;
|
|
|
|
|
|
|
|
if (job != NULL) {
|
|
|
|
pbm = &job->bm;
|
|
|
|
} else {
|
|
|
|
pbm = &Mybm;
|
|
|
|
}
|
|
|
|
if (pbm->mfp != NULL) {
|
|
|
|
meta_cmd_finish(pbm);
|
|
|
|
fclose(pbm->mfp);
|
|
|
|
pbm->mfp = NULL;
|
2010-12-16 02:03:35 +03:00
|
|
|
pbm->meta_fname[0] = '\0';
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch a full line from fp - growing bufp if needed
|
|
|
|
* Return length in bufp.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
|
|
|
|
{
|
|
|
|
char *buf = *bufp;
|
|
|
|
size_t bufsz = *szp;
|
|
|
|
struct stat fs;
|
|
|
|
int x;
|
|
|
|
|
|
|
|
if (fgets(&buf[o], bufsz - o, fp) != NULL) {
|
|
|
|
check_newline:
|
|
|
|
x = o + strlen(&buf[o]);
|
|
|
|
if (buf[x - 1] == '\n')
|
|
|
|
return x;
|
|
|
|
/*
|
|
|
|
* We need to grow the buffer.
|
|
|
|
* The meta file can give us a clue.
|
|
|
|
*/
|
|
|
|
if (fstat(fileno(fp), &fs) == 0) {
|
|
|
|
size_t newsz;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
|
|
|
|
if (newsz <= bufsz)
|
|
|
|
newsz = ROUNDUP(fs.st_size, BUFSIZ);
|
|
|
|
if (DEBUG(META))
|
2011-06-11 06:10:48 +04:00
|
|
|
fprintf(debug_file, "growing buffer %zu -> %zu\n",
|
|
|
|
bufsz, newsz);
|
2010-09-13 19:36:57 +04:00
|
|
|
p = bmake_realloc(buf, newsz);
|
|
|
|
if (p) {
|
|
|
|
*bufp = buf = p;
|
|
|
|
*szp = bufsz = newsz;
|
|
|
|
/* fetch the rest */
|
|
|
|
if (!fgets(&buf[x], bufsz - x, fp))
|
|
|
|
return x; /* truncated! */
|
|
|
|
goto check_newline;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-05 00:38:31 +04:00
|
|
|
static int
|
|
|
|
prefix_match(void *p, void *q)
|
|
|
|
{
|
|
|
|
const char *prefix = p;
|
|
|
|
const char *path = q;
|
|
|
|
size_t n = strlen(prefix);
|
|
|
|
|
|
|
|
return (0 == strncmp(path, prefix, n));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
string_match(const void *p, const void *q)
|
|
|
|
{
|
|
|
|
const char *p1 = p;
|
|
|
|
const char *p2 = q;
|
|
|
|
|
|
|
|
return strcmp(p1, p2);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* When running with 'meta' functionality, a target can be out-of-date
|
|
|
|
* if any of the references in it's meta data file is more recent.
|
2010-12-10 01:30:16 +03:00
|
|
|
* We have to track the latestdir on a per-process basis.
|
2010-09-13 19:36:57 +04:00
|
|
|
*/
|
2010-12-10 01:30:16 +03:00
|
|
|
#define LDIR_VNAME_FMT ".meta.%d.ldir"
|
|
|
|
|
2011-06-23 01:13:12 +04:00
|
|
|
/*
|
|
|
|
* It is possible that a .meta file is corrupted,
|
|
|
|
* if we detect this we want to reproduce it.
|
|
|
|
* Setting oodate TRUE will have that effect.
|
|
|
|
*/
|
|
|
|
#define CHECK_VALID_META(p) if (!(p && *p)) { \
|
|
|
|
warnx("%s: %d: malformed", fname, lineno); \
|
|
|
|
oodate = TRUE; \
|
|
|
|
continue; \
|
|
|
|
}
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
Boolean
|
|
|
|
meta_oodate(GNode *gn, Boolean oodate)
|
|
|
|
{
|
2010-12-10 01:30:16 +03:00
|
|
|
static char *tmpdir = NULL;
|
2011-03-06 20:41:11 +03:00
|
|
|
static char cwd[MAXPATHLEN];
|
2010-12-10 01:30:16 +03:00
|
|
|
char ldir_vname[64];
|
2010-09-13 19:36:57 +04:00
|
|
|
char latestdir[MAXPATHLEN];
|
|
|
|
char fname[MAXPATHLEN];
|
|
|
|
char fname1[MAXPATHLEN];
|
2010-12-10 01:30:16 +03:00
|
|
|
char fname2[MAXPATHLEN];
|
2010-09-13 19:36:57 +04:00
|
|
|
char *p;
|
|
|
|
char *cp;
|
2011-03-06 20:41:11 +03:00
|
|
|
static size_t cwdlen = 0;
|
2011-02-08 08:29:13 +03:00
|
|
|
static size_t tmplen = 0;
|
2010-09-13 19:36:57 +04:00
|
|
|
FILE *fp;
|
2013-01-19 08:23:37 +04:00
|
|
|
Boolean needOODATE = FALSE;
|
2011-05-05 00:38:31 +04:00
|
|
|
Lst missingFiles;
|
|
|
|
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
if (oodate)
|
|
|
|
return oodate; /* we're done */
|
|
|
|
|
2011-05-05 00:38:31 +04:00
|
|
|
missingFiles = Lst_Init(FALSE);
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* We need to check if the target is out-of-date. This includes
|
|
|
|
* checking if the expanded command has changed. This in turn
|
|
|
|
* requires that all variables are set in the same way that they
|
|
|
|
* would be if the target needs to be re-built.
|
|
|
|
*/
|
|
|
|
Make_DoAllVar(gn);
|
|
|
|
|
|
|
|
meta_name(gn, fname, sizeof(fname), NULL, NULL);
|
|
|
|
|
2011-02-14 00:22:41 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "meta_oodate: %s\n", fname);
|
|
|
|
#endif
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
if ((fp = fopen(fname, "r")) != NULL) {
|
|
|
|
static char *buf = NULL;
|
|
|
|
static size_t bufsz;
|
|
|
|
int lineno = 0;
|
2010-12-10 01:30:16 +03:00
|
|
|
int lastpid = 0;
|
|
|
|
int pid;
|
2010-09-13 19:36:57 +04:00
|
|
|
int f = 0;
|
|
|
|
int x;
|
|
|
|
LstNode ln;
|
|
|
|
struct stat fs;
|
|
|
|
|
|
|
|
if (!buf) {
|
|
|
|
bufsz = 8 * BUFSIZ;
|
|
|
|
buf = bmake_malloc(bufsz);
|
|
|
|
}
|
2010-12-10 01:30:16 +03:00
|
|
|
|
2011-03-06 20:41:11 +03:00
|
|
|
if (!cwdlen) {
|
|
|
|
if (getcwd(cwd, sizeof(cwd)) == NULL)
|
|
|
|
err(1, "Could not get current working directory");
|
|
|
|
cwdlen = strlen(cwd);
|
|
|
|
}
|
|
|
|
|
2010-12-10 01:30:16 +03:00
|
|
|
if (!tmpdir) {
|
|
|
|
tmpdir = getTmpdir();
|
|
|
|
tmplen = strlen(tmpdir);
|
|
|
|
}
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/* we want to track all the .meta we read */
|
|
|
|
Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
|
|
|
|
|
|
|
|
ln = Lst_First(gn->commands);
|
|
|
|
while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
|
|
|
|
lineno++;
|
|
|
|
if (buf[x - 1] == '\n')
|
|
|
|
buf[x - 1] = '\0';
|
2011-06-23 01:13:12 +04:00
|
|
|
else {
|
2010-09-13 19:36:57 +04:00
|
|
|
warnx("%s: %d: line truncated at %u", fname, lineno, x);
|
2011-06-23 01:13:12 +04:00
|
|
|
oodate = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
/* Find the start of the build monitor section. */
|
|
|
|
if (!f) {
|
|
|
|
if (strncmp(buf, "-- filemon", 10) == 0) {
|
|
|
|
f = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (strncmp(buf, "# buildmon", 10) == 0) {
|
|
|
|
f = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Delimit the record type. */
|
|
|
|
p = buf;
|
2011-02-08 08:29:13 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf);
|
|
|
|
#endif
|
2010-09-13 19:36:57 +04:00
|
|
|
strsep(&p, " ");
|
|
|
|
if (f) {
|
2010-12-10 01:30:16 +03:00
|
|
|
/*
|
|
|
|
* We are in the 'filemon' output section.
|
|
|
|
* Each record from filemon follows the general form:
|
|
|
|
*
|
|
|
|
* <key> <pid> <data>
|
|
|
|
*
|
|
|
|
* Where:
|
|
|
|
* <key> is a single letter, denoting the syscall.
|
|
|
|
* <pid> is the process that made the syscall.
|
|
|
|
* <data> is the arguments (of interest).
|
|
|
|
*/
|
|
|
|
switch(buf[0]) {
|
|
|
|
case '#': /* comment */
|
|
|
|
case 'V': /* version */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* We need to track pathnames per-process.
|
|
|
|
*
|
|
|
|
* Each process run by make, starts off in the 'CWD'
|
|
|
|
* recorded in the .meta file, if it chdirs ('C')
|
|
|
|
* elsewhere we need to track that - but only for
|
|
|
|
* that process. If it forks ('F'), we initialize
|
|
|
|
* the child to have the same cwd as its parent.
|
|
|
|
*
|
|
|
|
* We also need to track the 'latestdir' of
|
|
|
|
* interest. This is usually the same as cwd, but
|
|
|
|
* not if a process is reading directories.
|
|
|
|
*
|
|
|
|
* Each time we spot a different process ('pid')
|
|
|
|
* we save the current value of 'latestdir' in a
|
|
|
|
* variable qualified by 'lastpid', and
|
|
|
|
* re-initialize 'latestdir' to any pre-saved
|
|
|
|
* value for the current 'pid' and 'CWD' if none.
|
|
|
|
*/
|
2011-06-23 01:13:12 +04:00
|
|
|
CHECK_VALID_META(p);
|
2010-12-10 01:30:16 +03:00
|
|
|
pid = atoi(p);
|
|
|
|
if (pid > 0 && pid != lastpid) {
|
|
|
|
char *ldir;
|
|
|
|
char *tp;
|
|
|
|
|
|
|
|
if (lastpid > 0) {
|
|
|
|
/* We need to remember this. */
|
|
|
|
Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0);
|
|
|
|
}
|
|
|
|
snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
|
|
|
|
lastpid = pid;
|
|
|
|
ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
|
|
|
|
if (ldir) {
|
|
|
|
strlcpy(latestdir, ldir, sizeof(latestdir));
|
|
|
|
if (tp)
|
|
|
|
free(tp);
|
|
|
|
} else
|
|
|
|
strlcpy(latestdir, cwd, sizeof(latestdir));
|
|
|
|
}
|
|
|
|
/* Skip past the pid. */
|
|
|
|
if (strsep(&p, " ") == NULL)
|
|
|
|
continue;
|
2011-02-08 08:29:13 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, latestdir);
|
|
|
|
#endif
|
2010-12-10 01:30:16 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-06-23 01:13:12 +04:00
|
|
|
CHECK_VALID_META(p);
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/* Process according to record type. */
|
|
|
|
switch (buf[0]) {
|
2010-12-10 01:30:16 +03:00
|
|
|
case 'X': /* eXit */
|
|
|
|
Var_Delete(ldir_vname, VAR_GLOBAL);
|
|
|
|
lastpid = 0; /* no need to save ldir_vname */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'F': /* [v]Fork */
|
|
|
|
{
|
|
|
|
char cldir[64];
|
|
|
|
int child;
|
2010-09-13 19:36:57 +04:00
|
|
|
|
2010-12-10 01:30:16 +03:00
|
|
|
child = atoi(p);
|
|
|
|
if (child > 0) {
|
|
|
|
snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
|
|
|
|
Var_Set(cldir, latestdir, VAR_GLOBAL, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'C': /* Chdir */
|
2010-09-13 19:36:57 +04:00
|
|
|
/* Update the latest directory. */
|
|
|
|
strlcpy(latestdir, p, sizeof(latestdir));
|
|
|
|
break;
|
|
|
|
|
2011-05-05 00:38:31 +04:00
|
|
|
case 'M': /* renaMe */
|
|
|
|
if (Lst_IsEmpty(missingFiles))
|
|
|
|
break;
|
|
|
|
/* 'L' and 'M' put single quotes around the args */
|
|
|
|
if (*p == '\'') {
|
|
|
|
char *ep;
|
|
|
|
|
|
|
|
p++;
|
|
|
|
if ((ep = strchr(p, '\'')))
|
|
|
|
*ep = '\0';
|
|
|
|
}
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case 'D': /* unlink */
|
|
|
|
if (*p == '/' && !Lst_IsEmpty(missingFiles)) {
|
|
|
|
/* remove p from the missingFiles list if present */
|
|
|
|
if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) {
|
|
|
|
char *tp = Lst_Datum(ln);
|
|
|
|
Lst_Remove(missingFiles, ln);
|
|
|
|
free(tp);
|
2013-03-24 03:39:47 +04:00
|
|
|
ln = NULL; /* we're done with it */
|
2011-05-05 00:38:31 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'L': /* Link */
|
|
|
|
/* we want the target */
|
|
|
|
if (strsep(&p, " ") == NULL)
|
|
|
|
continue;
|
2011-06-23 01:13:12 +04:00
|
|
|
CHECK_VALID_META(p);
|
2011-05-05 00:38:31 +04:00
|
|
|
/* 'L' and 'M' put single quotes around the args */
|
|
|
|
if (*p == '\'') {
|
|
|
|
char *ep;
|
|
|
|
|
|
|
|
p++;
|
|
|
|
if ((ep = strchr(p, '\'')))
|
|
|
|
*ep = '\0';
|
|
|
|
}
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case 'W': /* Write */
|
|
|
|
/*
|
|
|
|
* If a file we generated within our bailiwick
|
|
|
|
* but outside of .OBJDIR is missing,
|
|
|
|
* we need to do it again.
|
|
|
|
*/
|
|
|
|
/* ignore non-absolute paths */
|
|
|
|
if (*p != '/')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (Lst_IsEmpty(metaBailiwick))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* ignore cwd - normal dependencies handle those */
|
|
|
|
if (strncmp(p, cwd, cwdlen) == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!Lst_ForEach(metaBailiwick, prefix_match, p))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* tmpdir might be within */
|
|
|
|
if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* ignore anything containing the string "tmp" */
|
|
|
|
if ((strstr("tmp", p)))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (stat(p, &fs) < 0) {
|
|
|
|
Lst_AtEnd(missingFiles, bmake_strdup(p));
|
|
|
|
}
|
|
|
|
break;
|
2010-12-10 01:30:16 +03:00
|
|
|
case 'R': /* Read */
|
|
|
|
case 'E': /* Exec */
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* Check for runtime files that can't
|
|
|
|
* be part of the dependencies because
|
|
|
|
* they are _expected_ to change.
|
|
|
|
*/
|
2013-06-25 04:20:54 +04:00
|
|
|
if (*p == '/' &&
|
|
|
|
Lst_ForEach(metaIgnorePaths, prefix_match, p)) {
|
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "meta_oodate: ignoring: %s\n",
|
|
|
|
p);
|
|
|
|
#endif
|
2013-06-25 01:16:02 +04:00
|
|
|
break;
|
2013-06-25 04:20:54 +04:00
|
|
|
}
|
2013-06-25 01:16:02 +04:00
|
|
|
|
2011-09-02 20:26:21 +04:00
|
|
|
if ((cp = strrchr(p, '/'))) {
|
|
|
|
cp++;
|
|
|
|
/*
|
|
|
|
* We don't normally expect to see this,
|
|
|
|
* but we do expect it to change.
|
|
|
|
*/
|
|
|
|
if (strcmp(cp, makeDependfile) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
2010-12-10 01:30:16 +03:00
|
|
|
* The rest of the record is the file name.
|
|
|
|
* Check if it's not an absolute path.
|
2010-09-13 19:36:57 +04:00
|
|
|
*/
|
2010-12-10 01:30:16 +03:00
|
|
|
{
|
|
|
|
char *sdirs[4];
|
|
|
|
char **sdp;
|
|
|
|
int sdx = 0;
|
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
if (*p == '/') {
|
|
|
|
sdirs[sdx++] = p; /* done */
|
|
|
|
} else {
|
|
|
|
if (strcmp(".", p) == 0)
|
|
|
|
continue; /* no point */
|
|
|
|
|
|
|
|
/* Check vs latestdir */
|
|
|
|
snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
|
|
|
|
sdirs[sdx++] = fname1;
|
|
|
|
|
|
|
|
if (strcmp(latestdir, cwd) != 0) {
|
|
|
|
/* Check vs cwd */
|
|
|
|
snprintf(fname2, sizeof(fname2), "%s/%s", cwd, p);
|
|
|
|
sdirs[sdx++] = fname2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sdirs[sdx++] = NULL;
|
2010-09-13 19:36:57 +04:00
|
|
|
|
2010-12-10 01:30:16 +03:00
|
|
|
for (sdp = sdirs; *sdp && !found; sdp++) {
|
2011-02-08 08:29:13 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp);
|
|
|
|
#endif
|
2010-12-10 01:30:16 +03:00
|
|
|
if (stat(*sdp, &fs) == 0) {
|
|
|
|
found = 1;
|
|
|
|
p = *sdp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
2011-02-08 08:29:13 +03:00
|
|
|
#ifdef DEBUG_META_MODE
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p);
|
|
|
|
#endif
|
2010-12-10 01:30:16 +03:00
|
|
|
if (!S_ISDIR(fs.st_mode) &&
|
|
|
|
fs.st_mtime > gn->mtime) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
|
|
|
|
oodate = TRUE;
|
|
|
|
} else if (S_ISDIR(fs.st_mode)) {
|
|
|
|
/* Update the latest directory. */
|
|
|
|
realpath(p, latestdir);
|
|
|
|
}
|
|
|
|
} else if (errno == ENOENT && *p == '/' &&
|
|
|
|
strncmp(p, cwd, cwdlen) != 0) {
|
|
|
|
/*
|
|
|
|
* A referenced file outside of CWD is missing.
|
|
|
|
* We cannot catch every eventuality here...
|
|
|
|
*/
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
if (DEBUG(META))
|
2010-12-10 01:30:16 +03:00
|
|
|
fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p);
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
oodate = TRUE;
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2010-12-10 01:30:16 +03:00
|
|
|
} else if (strcmp(buf, "CMD") == 0) {
|
2010-09-13 19:36:57 +04:00
|
|
|
/*
|
|
|
|
* Compare the current command with the one in the
|
|
|
|
* meta data file.
|
|
|
|
*/
|
|
|
|
if (ln == NULL) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
|
|
|
|
oodate = TRUE;
|
|
|
|
} else {
|
|
|
|
char *cmd = (char *)Lst_Datum(ln);
|
2013-03-31 09:49:51 +04:00
|
|
|
Boolean hasOODATE = FALSE;
|
|
|
|
|
|
|
|
if (strstr(cmd, "$?"))
|
|
|
|
hasOODATE = TRUE;
|
|
|
|
else if ((cp = strstr(cmd, ".OODATE"))) {
|
|
|
|
/* check for $[{(].OODATE[:)}] */
|
|
|
|
if (cp > cmd + 2 && cp[-2] == '$')
|
|
|
|
hasOODATE = TRUE;
|
|
|
|
}
|
|
|
|
if (hasOODATE) {
|
|
|
|
needOODATE = TRUE;
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno);
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
|
|
|
cmd = Var_Subst(NULL, cmd, gn, TRUE);
|
|
|
|
|
|
|
|
if ((cp = strchr(cmd, '\n'))) {
|
|
|
|
int n;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This command contains newlines, we need to
|
|
|
|
* fetch more from the .meta file before we
|
|
|
|
* attempt a comparison.
|
|
|
|
*/
|
|
|
|
/* first put the newline back at buf[x - 1] */
|
|
|
|
buf[x - 1] = '\n';
|
|
|
|
do {
|
|
|
|
/* now fetch the next line */
|
|
|
|
if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
|
|
|
|
break;
|
|
|
|
x = n;
|
|
|
|
lineno++;
|
|
|
|
if (buf[x - 1] != '\n') {
|
|
|
|
warnx("%s: %d: line truncated at %u", fname, lineno, x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cp = strchr(++cp, '\n');
|
|
|
|
} while (cp);
|
|
|
|
if (buf[x - 1] == '\n')
|
|
|
|
buf[x - 1] = '\0';
|
|
|
|
}
|
2013-03-31 09:49:51 +04:00
|
|
|
if (!hasOODATE &&
|
2010-09-13 19:36:57 +04:00
|
|
|
!(gn->type & OP_NOMETA_CMP) &&
|
|
|
|
strcmp(p, cmd) != 0) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
|
|
|
|
if (!metaIgnoreCMDs)
|
|
|
|
oodate = TRUE;
|
|
|
|
}
|
|
|
|
free(cmd);
|
|
|
|
ln = Lst_Succ(ln);
|
|
|
|
}
|
|
|
|
} else if (strcmp(buf, "CWD") == 0) {
|
2011-03-29 21:29:20 +04:00
|
|
|
/*
|
|
|
|
* Check if there are extra commands now
|
|
|
|
* that weren't in the meta data file.
|
|
|
|
*/
|
|
|
|
if (!oodate && ln != NULL) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
|
|
|
|
oodate = TRUE;
|
|
|
|
}
|
2011-03-06 20:41:11 +03:00
|
|
|
if (strcmp(p, cwd) != 0) {
|
2010-09-13 19:36:57 +04:00
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
|
|
|
|
oodate = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fp);
|
2011-05-05 00:38:31 +04:00
|
|
|
if (!Lst_IsEmpty(missingFiles)) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: missing files: %s...\n",
|
|
|
|
fname, (char *)Lst_Datum(Lst_First(missingFiles)));
|
|
|
|
oodate = TRUE;
|
|
|
|
Lst_Destroy(missingFiles, (FreeProc *)free);
|
|
|
|
}
|
2011-08-18 04:00:21 +04:00
|
|
|
} else {
|
|
|
|
if ((gn->type & OP_META)) {
|
|
|
|
if (DEBUG(META))
|
|
|
|
fprintf(debug_file, "%s: required but missing\n", fname);
|
|
|
|
oodate = TRUE;
|
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
}
|
2013-01-19 08:23:37 +04:00
|
|
|
if (oodate && needOODATE) {
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
/*
|
2013-01-19 08:23:37 +04:00
|
|
|
* Target uses .OODATE which is empty; or we wouldn't be here.
|
|
|
|
* We have decided it is oodate, so .OODATE needs to be set.
|
|
|
|
* All we can sanely do is set it to .ALLSRC.
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
*/
|
|
|
|
Var_Delete(OODATE, gn);
|
2013-01-19 08:23:37 +04:00
|
|
|
Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0);
|
|
|
|
if (cp)
|
|
|
|
free(cp);
|
When a source file moves, make will ignore the stale dependency,
but if the file in question is one that needs to be compiled (.c or .cc),
it still hands the bogus name to the compiler.
If Dir_MTime() cannot find such a file (gn->iParents is not empty),
see if the basename can be found via .PATH, and if so set gn->path to
the found file. This prevents the stale path being given to the
compiler.
In meta_oodate(), if a referenced file no longer exists, consider the
target out-of-date.
Also, if meta_oodate() decides a target is out-of-date, and it
it uses .OODATE in its commands, we need .OODATE recomputed.
Undo our call to Make_DoAllVar() so that the call from Make_OODate()
will do the right thing.
2010-11-27 08:02:35 +03:00
|
|
|
}
|
2010-09-13 19:36:57 +04:00
|
|
|
return oodate;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* support for compat mode */
|
|
|
|
|
|
|
|
static int childPipe[2];
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_compat_start(void)
|
|
|
|
{
|
|
|
|
#ifdef USE_FILEMON_ONCE
|
|
|
|
/*
|
|
|
|
* We need to re-open filemon for each cmd.
|
|
|
|
*/
|
|
|
|
BuildMon *pbm = &Mybm;
|
|
|
|
|
|
|
|
if (pbm->mfp != NULL && useFilemon) {
|
|
|
|
filemon_open(pbm);
|
|
|
|
} else {
|
|
|
|
pbm->mon_fd = pbm->filemon_fd = -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (pipe(childPipe) < 0)
|
|
|
|
Punt("Cannot create pipe: %s", strerror(errno));
|
|
|
|
/* Set close-on-exec flag for both */
|
|
|
|
(void)fcntl(childPipe[0], F_SETFD, 1);
|
|
|
|
(void)fcntl(childPipe[1], F_SETFD, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_compat_child(void)
|
|
|
|
{
|
|
|
|
meta_job_child(NULL);
|
|
|
|
if (dup2(childPipe[1], 1) < 0 ||
|
|
|
|
dup2(1, 2) < 0) {
|
|
|
|
execError("dup2", "pipe");
|
|
|
|
_exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_compat_parent(void)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
|
|
|
|
close(childPipe[1]); /* child side */
|
|
|
|
fp = fdopen(childPipe[0], "r");
|
|
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
|
|
meta_job_output(NULL, buf, "");
|
|
|
|
printf("%s", buf);
|
|
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
}
|
2010-09-14 01:31:59 +04:00
|
|
|
|
|
|
|
#endif /* USE_META */
|