RTManifest.cpp revision c58f1213e628a545081c70e26c6b67a841cff880
/* $Id$ */
/** @file
* IPRT - Manifest Utility.
*/
/*
* Copyright (C) 2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/manifest.h>
#include <iprt/buildconfig.h>
#include <iprt/err.h>
#include <iprt/file.h>
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/message.h>
#include <iprt/path.h>
#include <iprt/process.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/vfs.h>
/**
* Verify a manifest.
*
* @returns Program exit code, failures with error message.
* @param pszManifest The manifest file. NULL if standard input.
* @param fStdFormat Whether to expect standard format (true) or
* java format (false).
* @param pszChDir The directory to change into before processing
* the files in the manifest.
*/
static RTEXITCODE rtManifestDoVerify(const char *pszManifest, bool fStdFormat, const char *pszChDir)
{
/*
* Open the manifest.
*/
int rc;
RTVFSIOSTREAM hVfsIos;
if (!pszManifest)
{
rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, RTFILE_O_READ, false /*fLeaveOpen*/, &hVfsIos);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard input for reading: %Rrc", rc);
}
else
{
const char *pszError;
rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, &hVfsIos, &pszError);
if (RT_FAILURE(rc))
{
if (pszError && *pszError)
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsChainOpenIoStream failed with rc=%Rrc:\n"
" '%s'\n",
" %*s^\n",
rc, pszManifest, pszError - pszManifest, "");
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed with %Rrc opening the input manifest '%s'", rc, pszManifest);
}
}
/*
* Read it.
*/
RTMANIFEST hManifest;
rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
if (RT_SUCCESS(rc))
{
if (fStdFormat)
{
char szErr[4096 + 1024];
rc = RTManifestReadStandardEx(hManifest, hVfsIos, szErr, sizeof(szErr));
if (RT_SUCCESS(rc))
{
RTVfsIoStrmRelease(hVfsIos);
hVfsIos = NIL_RTVFSIOSTREAM;
/*
* Do the verification.
*/
/** @todo We're missing some enumeration APIs here! */
RTMsgError("The manifest read fine, but the actual verification code is yet to be written. Sorry.");
rc = VERR_NOT_IMPLEMENTED;
#if 1 /* For now, just write the manifest to stdout so we can test the read routine. */
RTVFSIOSTREAM hVfsIosOut;
rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIosOut);
if (RT_SUCCESS(rc))
{
RTManifestWriteStandard(hManifest, hVfsIosOut);
RTVfsIoStrmRelease(hVfsIosOut);
}
#endif
}
else if (szErr[0])
RTMsgError("Error reading manifest: %s", szErr);
else
RTMsgError("Error reading manifest: %Rrc", rc);
}
else
{
RTMsgError("Support for Java manifest files is not implemented yet");
rc = VERR_NOT_IMPLEMENTED;
}
RTManifestRelease(hManifest);
}
RTVfsIoStrmRelease(hVfsIos);
return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}
/**
* Adds a file to the manifest.
*
* @returns IPRT status code, failures with error message.
* @param hManifest The manifest to add it to.
* @param pszFilename The name of the file to add.
* @param fAttr The manifest attributes to add.
*/
static int rtManifestAddFileToManifest(RTMANIFEST hManifest, const char *pszFilename, uint32_t fAttr)
{
RTVFSIOSTREAM hVfsIos;
const char *pszError;
int rc = RTVfsChainOpenIoStream(pszFilename, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, &hVfsIos, &pszError);
if (RT_FAILURE(rc))
{
if (pszError && *pszError)
RTMsgError("RTVfsChainOpenIoStream failed with rc=%Rrc:\n"
" '%s'\n",
" %*s^\n",
rc, pszFilename, pszError - pszFilename, "");
else
RTMsgError("Failed with %Rrc opening '%s'", rc, pszFilename);
return rc;
}
rc = RTManifestEntryAddIoStream(hManifest, hVfsIos, pszFilename, fAttr);
if (RT_FAILURE(rc))
RTMsgError("RTManifestEntryAddIoStream failed for '%s': %Rrc", rc);
RTVfsIoStrmRelease(hVfsIos);
return rc;
}
/**
* Create a manifest from the specified input files.
*
* @returns Program exit code, failures with error message.
* @param pszManifest The name of the output manifest file. NULL if
* it should be written to standard output.
* @param fStdFormat Whether to expect standard format (true) or
* java format (false).
* @param pszChDir The directory to change into before processing
* the file arguments.
* @param fAttr The file attributes to put in the manifest.
* @param pGetState The RTGetOpt state.
* @param pUnion What the last RTGetOpt() call returned.
* @param chOpt What the last RTGetOpt() call returned.
*/
static RTEXITCODE rtManifestDoCreate(const char *pszManifest, bool fStdFormat, const char *pszChDir, uint32_t fAttr,
PRTGETOPTSTATE pGetState, PRTGETOPTUNION pUnion, int chOpt)
{
/*
* Open the manifest file.
*/
int rc;
RTVFSIOSTREAM hVfsIos;
if (!pszManifest)
{
rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIos);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard output for writing: %Rrc", rc);
}
else
{
const char *pszError;
rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
&hVfsIos, &pszError);
if (RT_FAILURE(rc))
{
if (pszError && *pszError)
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsChainOpenIoStream failed with rc=%Rrc:\n"
" '%s'\n",
" %*s^\n",
rc, pszManifest, pszError - pszManifest, "");
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed with %Rrc opening the manifest '%s'", rc, pszManifest);
}
}
/*
* Create the internal manifest.
*/
RTMANIFEST hManifest;
rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
if (RT_SUCCESS(rc))
{
/*
* Change directory and start processing the specified files.
*/
if (pszChDir)
{
rc = RTPathSetCurrent(pszChDir);
if (RT_FAILURE(rc))
RTMsgError("Failed to change directory to '%s': %Rrc", pszChDir, rc);
}
if (RT_SUCCESS(rc))
{
while (chOpt == VINF_GETOPT_NOT_OPTION)
{
rc = rtManifestAddFileToManifest(hManifest, pUnion->psz, fAttr);
if (RT_FAILURE(rc))
break;
/* next */
chOpt = RTGetOpt(pGetState, pUnion);
}
if (RT_SUCCESS(rc) && chOpt != 0)
{
RTGetOptPrintError(chOpt, pUnion);
rc = chOpt < 0 ? chOpt : -chOpt;
}
}
/*
* Write the manifest.
*/
if (RT_SUCCESS(rc))
{
if (fStdFormat)
{
rc = RTManifestWriteStandard(hManifest, hVfsIos);
if (RT_FAILURE(rc))
RTMsgError("RTManifestWriteStandard failed: %Rrc", rc);
}
else
{
RTMsgError("Support for Java manifest files is not implemented yet");
rc = VERR_NOT_IMPLEMENTED;
}
}
RTManifestRelease(hManifest);
}
RTVfsIoStrmRelease(hVfsIos);
return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
}
int main(int argc, char **argv)
{
int rc = RTR3InitExe(argc, &argv, 0);
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
/*
* Parse arguments.
*/
static RTGETOPTDEF const s_aOptions[] =
{
{ "--manifest", 'm', RTGETOPT_REQ_STRING },
{ "--java", 'j', RTGETOPT_REQ_NOTHING },
{ "--chdir", 'C', RTGETOPT_REQ_STRING },
{ "--attribute", 'a', RTGETOPT_REQ_STRING },
{ "--verify", 'v', RTGETOPT_REQ_NOTHING },
};
bool fVerify = false;
bool fStdFormat = true;
const char *pszManifest = NULL;
const char *pszChDir = NULL;
uint32_t fAttr = RTMANIFEST_ATTR_UNKNOWN;
RTGETOPTSTATE GetState;
rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
RTGETOPTUNION ValueUnion;
while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
&& rc != VINF_GETOPT_NOT_OPTION)
{
switch (rc)
{
case 'a':
{
static struct
{
const char *pszAttr;
uint32_t fAttr;
} s_aAttributes[] =
{
{ "size", RTMANIFEST_ATTR_SIZE },
{ "md5", RTMANIFEST_ATTR_MD5 },
{ "sha1", RTMANIFEST_ATTR_SHA1 },
{ "sha256", RTMANIFEST_ATTR_SHA256 },
{ "sha512", RTMANIFEST_ATTR_SHA512 }
};
uint32_t fThisAttr = RTMANIFEST_ATTR_UNKNOWN;
for (unsigned i = 0; i < RT_ELEMENTS(s_aAttributes); i++)
if (!RTStrICmp(s_aAttributes[i].pszAttr, ValueUnion.psz))
{
fThisAttr = s_aAttributes[i].fAttr;
break;
}
if (fThisAttr == RTMANIFEST_ATTR_UNKNOWN)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown attribute type '%s'", ValueUnion.psz);
if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
fAttr = fThisAttr;
else
fAttr |= fThisAttr;
break;
}
case 'j':
fStdFormat = false;
break;
case 'm':
if (pszManifest)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one manifest can be specified");
pszManifest = ValueUnion.psz;
break;
case 'v':
fVerify = true;
break;
case 'C':
if (pszChDir)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one directory change can be specified");
pszChDir = ValueUnion.psz;
break;
case 'h':
RTPrintf("Usage: %s [--manifest <file>] [--chdir <dir>] [--attribute <attrib-name> [..]] <files>\n"
" or %s --verify [--manifest <file>] [--chdir <dir>]\n"
"\n"
"attrib-name: size, md5, sha1, sha256 or sha512\n"
, RTProcShortName(), RTProcShortName());
return RTEXITCODE_SUCCESS;
case 'V':
RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
return RTEXITCODE_SUCCESS;
default:
return RTGetOptPrintError(rc, &ValueUnion);
}
}
/*
* Take action.
*/
RTEXITCODE rcExit;
if (!fVerify)
{
if (rc != VINF_GETOPT_NOT_OPTION)
RTMsgWarning("No files specified, the manifest will be empty.");
if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
fAttr = RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_MD5
| RTMANIFEST_ATTR_SHA1 | RTMANIFEST_ATTR_SHA256 | RTMANIFEST_ATTR_SHA512;
rcExit = rtManifestDoCreate(pszManifest, fStdFormat, pszChDir, fAttr, &GetState, &ValueUnion, rc);
}
else
{
if (rc == VINF_GETOPT_NOT_OPTION)
return RTMsgErrorExit(RTEXITCODE_SYNTAX,
"No files should be specified when verifying a manifest (--verfiy), "
"only a manifest via the --manifest option");
if (fAttr != RTMANIFEST_ATTR_UNKNOWN)
return RTMsgErrorExit(RTEXITCODE_SYNTAX,
"The --attribute (-a) option does not combine with --verify (-v)");
rcExit = rtManifestDoVerify(pszManifest, fStdFormat, pszChDir);
}
return rcExit;
}