// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Finds crate binaries and loads their metadata use lib::llvm::{False, llvm, mk_object_file, mk_section_iter}; use metadata::decoder; use metadata::encoder; use metadata::filesearch::{FileSearch, FileMatch, FileMatches, FileDoesntMatch}; use metadata::filesearch; use syntax::codemap::Span; use syntax::diagnostic::span_handler; use syntax::parse::token::ident_interner; use syntax::print::pprust; use syntax::{ast, attr}; use syntax::attr::AttrMetaMethods; use std::c_str::ToCStr; use std::cast; use std::io; use std::num; use std::option; use std::os::consts::{macos, freebsd, linux, android, win32}; use std::ptr; use std::str; use std::vec; use extra::flate; pub enum Os { OsMacos, OsWin32, OsLinux, OsAndroid, OsFreebsd } pub struct Context { diag: @mut span_handler, filesearch: @FileSearch, span: Span, ident: @str, metas: ~[@ast::MetaItem], hash: @str, os: Os, is_static: bool, intr: @ident_interner } pub fn load_library_crate(cx: &Context) -> (~str, @~[u8]) { match find_library_crate(cx) { Some(t) => t, None => { cx.diag.span_fatal(cx.span, fmt!("can't find crate for `%s`", cx.ident)); } } } fn find_library_crate(cx: &Context) -> Option<(~str, @~[u8])> { attr::require_unique_names(cx.diag, cx.metas); find_library_crate_aux(cx, libname(cx), cx.filesearch) } fn libname(cx: &Context) -> (~str, ~str) { if cx.is_static { return (~"lib", ~".rlib"); } let (dll_prefix, dll_suffix) = match cx.os { OsWin32 => (win32::DLL_PREFIX, win32::DLL_SUFFIX), OsMacos => (macos::DLL_PREFIX, macos::DLL_SUFFIX), OsLinux => (linux::DLL_PREFIX, linux::DLL_SUFFIX), OsAndroid => (android::DLL_PREFIX, android::DLL_SUFFIX), OsFreebsd => (freebsd::DLL_PREFIX, freebsd::DLL_SUFFIX), }; (dll_prefix.to_owned(), dll_suffix.to_owned()) } fn find_library_crate_aux( cx: &Context, (prefix, suffix): (~str, ~str), filesearch: @filesearch::FileSearch ) -> Option<(~str, @~[u8])> { let crate_name = crate_name_from_metas(cx.metas); // want: crate_name.dir_part() + prefix + crate_name.file_part + "-" let prefix = fmt!("%s%s-", prefix, crate_name); let mut matches = ~[]; filesearch::search(filesearch, |path| -> FileMatch { let path_str = path.filename(); match path_str { None => FileDoesntMatch, Some(path_str) => if path_str.starts_with(prefix) && path_str.ends_with(suffix) { debug!("%s is a candidate", path.to_str()); match get_metadata_section(cx.os, path) { Some(cvec) => if !crate_matches(cvec, cx.metas, cx.hash) { debug!("skipping %s, metadata doesn't match", path.to_str()); FileDoesntMatch } else { debug!("found %s with matching metadata", path.to_str()); matches.push((path.to_str(), cvec)); FileMatches }, _ => { debug!("could not load metadata for %s", path.to_str()); FileDoesntMatch } } } else { FileDoesntMatch } } }); match matches.len() { 0 => None, 1 => Some(matches[0]), _ => { cx.diag.span_err( cx.span, fmt!("multiple matching crates for `%s`", crate_name)); cx.diag.handler().note("candidates:"); for pair in matches.iter() { let ident = pair.first(); let data = pair.second(); cx.diag.handler().note(fmt!("path: %s", ident)); let attrs = decoder::get_crate_attributes(data); note_linkage_attrs(cx.intr, cx.diag, attrs); } cx.diag.handler().abort_if_errors(); None } } } pub fn crate_name_from_metas(metas: &[@ast::MetaItem]) -> @str { for m in metas.iter() { match m.name_str_pair() { Some((name, s)) if "name" == name => { return s; } _ => {} } } fail!("expected to find the crate name") } pub fn package_id_from_metas(metas: &[@ast::MetaItem]) -> Option<@str> { for m in metas.iter() { match m.name_str_pair() { Some((name, s)) if "package_id" == name => { return Some(s); } _ => {} } } None } pub fn note_linkage_attrs(intr: @ident_interner, diag: @mut span_handler, attrs: ~[ast::Attribute]) { let r = attr::find_linkage_metas(attrs); for mi in r.iter() { diag.handler().note(fmt!("meta: %s", pprust::meta_item_to_str(*mi,intr))); } } fn crate_matches(crate_data: @~[u8], metas: &[@ast::MetaItem], hash: @str) -> bool { let attrs = decoder::get_crate_attributes(crate_data); let linkage_metas = attr::find_linkage_metas(attrs); if !hash.is_empty() { let chash = decoder::get_crate_hash(crate_data); if chash != hash { return false; } } metadata_matches(linkage_metas, metas) } pub fn metadata_matches(extern_metas: &[@ast::MetaItem], local_metas: &[@ast::MetaItem]) -> bool { // extern_metas: metas we read from the crate // local_metas: metas we're looking for debug!("matching %u metadata requirements against %u items", local_metas.len(), extern_metas.len()); do local_metas.iter().all |needed| { attr::contains(extern_metas, *needed) } } fn get_metadata_section(os: Os, filename: &Path) -> Option<@~[u8]> { unsafe { let mb = do filename.with_c_str |buf| { llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf) }; if mb as int == 0 { return option::None::<@~[u8]>; } let of = match mk_object_file(mb) { option::Some(of) => of, _ => return option::None::<@~[u8]> }; let si = mk_section_iter(of.llof); while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False { let name_buf = llvm::LLVMGetSectionName(si.llsi); let name = str::raw::from_c_str(name_buf); debug!("get_metadata_section: name %s", name); if read_meta_section_name(os) == name { let cbuf = llvm::LLVMGetSectionContents(si.llsi); let csz = llvm::LLVMGetSectionSize(si.llsi) as uint; let mut found = None; let cvbuf: *u8 = cast::transmute(cbuf); let vlen = encoder::metadata_encoding_version.len(); debug!("checking %u bytes of metadata-version stamp", vlen); let minsz = num::min(vlen, csz); let mut version_ok = false; do vec::raw::buf_as_slice(cvbuf, minsz) |buf0| { version_ok = (buf0 == encoder::metadata_encoding_version); } if !version_ok { return None; } let cvbuf1 = ptr::offset(cvbuf, vlen as int); debug!("inflating %u bytes of compressed metadata", csz - vlen); do vec::raw::buf_as_slice(cvbuf1, csz-vlen) |bytes| { let inflated = flate::inflate_bytes(bytes); found = Some(@(inflated)); } if found != None { return found; } } llvm::LLVMMoveToNextSection(si.llsi); } return option::None::<@~[u8]>; } } pub fn meta_section_name(os: Os) -> &'static str { match os { OsMacos => "__DATA,__note.rustc", OsWin32 => ".note.rustc", OsLinux => ".note.rustc", OsAndroid => ".note.rustc", OsFreebsd => ".note.rustc" } } pub fn read_meta_section_name(os: Os) -> &'static str { match os { OsMacos => "__note.rustc", OsWin32 => ".note.rustc", OsLinux => ".note.rustc", OsAndroid => ".note.rustc", OsFreebsd => ".note.rustc" } } // A diagnostic function for dumping crate metadata to an output stream pub fn list_file_metadata(intr: @ident_interner, os: Os, path: &Path, out: @io::Writer) { match get_metadata_section(os, path) { option::Some(bytes) => decoder::list_crate_metadata(intr, bytes, out), option::None => { out.write_str(fmt!("could not find metadata in %s.\n", path.to_str())) } } }