From e4b2ebd80779a44d24fe87af26ef278c1e2d97d2 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Fri, 6 Oct 2023 02:04:49 +0200 Subject: [PATCH] Add wrappers around boost::filesystem methods that handle the required mangling of long filenames on Windows. Also wrap lots of missing places (e.g. calls to asdcplib, libxml++, libcxml etc.) in dcp::filesystem::fix_long_path(). The idea is to keep paths un-mangled until they we call some filesystem-related API and mangle them at that point. Otherwise we end up serialising mangled names, which seems like it will not end well. Should fix DoM #2623. --- src/array_data.cc | 3 +- src/asset.cc | 32 +-- src/asset_factory.cc | 2 +- src/asset_map.cc | 9 +- src/asset_reader.h | 3 +- src/atmos_asset.cc | 2 +- src/atmos_asset_writer.cc | 3 +- src/certificate_chain.cc | 21 +- src/combine.cc | 21 +- src/cpl.cc | 5 +- src/dcp.cc | 18 +- src/file.cc | 46 +-- src/file.h | 3 - src/filesystem.cc | 329 ++++++++++++++++++++++ src/filesystem.h | 124 ++++++++ src/interop_subtitle_asset.cc | 3 +- src/mono_picture_asset.cc | 7 +- src/mono_picture_frame.cc | 3 +- src/picture_asset_writer_common.cc | 7 +- src/pkl.cc | 5 +- src/search.cc | 3 +- src/smpte_subtitle_asset.cc | 15 +- src/sound_asset.cc | 9 +- src/sound_asset_writer.cc | 3 +- src/stereo_picture_asset.cc | 7 +- src/subtitle_image.cc | 5 +- src/util.cc | 7 +- src/verify.cc | 15 +- src/wscript | 2 + test/encryption_test.cc | 6 + test/{file_test.cc => filesystem_test.cc} | 31 +- test/long_filenames_test.cc | 59 ++++ test/wscript | 3 +- tools/dcpdiff.cc | 7 +- tools/dcpinfo.cc | 3 +- tools/dcprecover.cc | 5 +- tools/dcpverify.cc | 3 +- 37 files changed, 672 insertions(+), 157 deletions(-) create mode 100644 src/filesystem.cc create mode 100644 src/filesystem.h rename test/{file_test.cc => filesystem_test.cc} (63%) create mode 100644 test/long_filenames_test.cc diff --git a/src/array_data.cc b/src/array_data.cc index 52a22cf6..e388454a 100644 --- a/src/array_data.cc +++ b/src/array_data.cc @@ -39,6 +39,7 @@ #include "array_data.h" #include "file.h" +#include "filesystem.h" #include "exceptions.h" #include "util.h" #include @@ -71,7 +72,7 @@ ArrayData::ArrayData (uint8_t const * data, int size) ArrayData::ArrayData (boost::filesystem::path file) { - auto const size = boost::filesystem::file_size (file); + auto const size = filesystem::file_size(file); _data = std::make_shared>(size); File f(file, "rb"); diff --git a/src/asset.cc b/src/asset.cc index 213d4878..15f81015 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -43,6 +43,7 @@ #include "dcp_assert.h" #include "equality_options.h" #include "exceptions.h" +#include "filesystem.h" #include "pkl.h" #include "raw_convert.h" #include "util.h" @@ -57,7 +58,6 @@ using std::string; using boost::function; using std::shared_ptr; using boost::optional; -using namespace boost::filesystem; using namespace dcp; @@ -67,14 +67,14 @@ Asset::Asset () } -Asset::Asset (path file) +Asset::Asset(boost::filesystem::path file) : _file (file) { } -Asset::Asset (string id, path file) +Asset::Asset(string id, boost::filesystem::path file) : Object (id) , _file (file) { @@ -83,13 +83,13 @@ Asset::Asset (string id, path file) void -Asset::add_to_pkl (shared_ptr pkl, path root) const +Asset::add_to_pkl(shared_ptr pkl, boost::filesystem::path root) const { DCP_ASSERT (_file); auto path = relative_to_root ( - canonical(root), - canonical(_file.get()) + filesystem::canonical(root), + filesystem::canonical(_file.get()) ); if (!path) { @@ -104,7 +104,7 @@ Asset::add_to_pkl (shared_ptr pkl, path root) const void -Asset::add_to_assetmap (AssetMap& asset_map, path root) const +Asset::add_to_assetmap(AssetMap& asset_map, boost::filesystem::path root) const { DCP_ASSERT (_file); add_file_to_assetmap (asset_map, root, _file.get(), _id); @@ -112,11 +112,11 @@ Asset::add_to_assetmap (AssetMap& asset_map, path root) const void -Asset::add_file_to_assetmap (AssetMap& asset_map, path root, path file, string id) +Asset::add_file_to_assetmap(AssetMap& asset_map, boost::filesystem::path root, boost::filesystem::path file, string id) { auto path = relative_to_root ( - canonical(root), - canonical(file) + filesystem::canonical(root), + filesystem::canonical(file) ); if (!path) { @@ -160,24 +160,24 @@ Asset::equals(std::shared_ptr other, EqualityOptions const& opt, No void -Asset::set_file (path file) const +Asset::set_file(boost::filesystem::path file) const { - _file = absolute (file); + _file = filesystem::absolute(file); _hash = optional(); } void -Asset::set_file_preserving_hash(path file) const +Asset::set_file_preserving_hash(boost::filesystem::path file) const { - _file = absolute(file); + _file = filesystem::absolute(file); } void -Asset::rename_file(path file) +Asset::rename_file(boost::filesystem::path file) { - _file = absolute(file); + _file = filesystem::absolute(file); } diff --git a/src/asset_factory.cc b/src/asset_factory.cc index 2ab79899..1f830cce 100644 --- a/src/asset_factory.cc +++ b/src/asset_factory.cc @@ -61,7 +61,7 @@ dcp::asset_factory (boost::filesystem::path path, bool ignore_incorrect_picture_ */ ASDCP::EssenceType_t type; - auto const result = ASDCP::EssenceType(path.string().c_str(), type); + auto const result = ASDCP::EssenceType(dcp::filesystem::fix_long_path(path).string().c_str(), type); if (result != ASDCP::RESULT_OK) { throw ReadError(String::compose("Could not find essence type (%1)", result.Message()), path.string()); } diff --git a/src/asset_map.cc b/src/asset_map.cc index c1bafd92..281f27c3 100644 --- a/src/asset_map.cc +++ b/src/asset_map.cc @@ -35,6 +35,7 @@ #include "asset_map.h" #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include "raw_convert.h" #include "warnings.h" LIBDCP_DISABLE_WARNINGS @@ -60,7 +61,7 @@ AssetMap::AssetMap(boost::filesystem::path file) { cxml::Document doc("AssetMap"); - doc.read_file(file); + doc.read_file(dcp::filesystem::fix_long_path(file)); if (doc.namespace_uri() == assetmap_interop_ns) { _standard = Standard::INTEROP; } else if (doc.namespace_uri() == assetmap_smpte_ns) { @@ -191,7 +192,7 @@ AssetMap::write_xml(boost::filesystem::path file) const asset.write_xml(asset_list, file.parent_path()); } - doc.write_to_file_formatted(file.string(), "UTF-8"); + doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8"); _file = file; } @@ -207,12 +208,12 @@ AssetMap::Asset::write_xml(xmlpp::Element* asset_list, boost::filesystem::path d auto chunk_list = node->add_child("ChunkList"); auto chunk = chunk_list->add_child("Chunk"); - auto relative_path = relative_to_root(boost::filesystem::canonical(dcp_root_directory), boost::filesystem::canonical(_path)); + auto relative_path = relative_to_root(filesystem::canonical(dcp_root_directory), filesystem::canonical(_path)); DCP_ASSERT(relative_path); chunk->add_child("Path")->add_child_text(relative_path->generic_string()); chunk->add_child("VolumeIndex")->add_child_text("1"); chunk->add_child("Offset")->add_child_text("0"); - chunk->add_child("Length")->add_child_text(raw_convert(boost::filesystem::file_size(_path))); + chunk->add_child("Length")->add_child_text(raw_convert(filesystem::file_size(_path))); } diff --git a/src/asset_reader.h b/src/asset_reader.h index 8478b51f..c8953e09 100644 --- a/src/asset_reader.h +++ b/src/asset_reader.h @@ -44,6 +44,7 @@ #include "asset.h" #include "crypto_context.h" #include "dcp_assert.h" +#include "filesystem.h" #include #include @@ -98,7 +99,7 @@ private: { _reader = new R (); DCP_ASSERT (asset->file()); - auto const r = _reader->OpenRead (asset->file()->string().c_str()); + auto const r = _reader->OpenRead(dcp::filesystem::fix_long_path(*asset->file()).string().c_str()); if (ASDCP_FAILURE(r)) { delete _reader; boost::throw_exception (FileError("could not open MXF file for reading", asset->file().get(), r)); diff --git a/src/atmos_asset.cc b/src/atmos_asset.cc index ae381737..42a0774e 100644 --- a/src/atmos_asset.cc +++ b/src/atmos_asset.cc @@ -68,7 +68,7 @@ AtmosAsset::AtmosAsset (boost::filesystem::path file) , MXF (Standard::SMPTE) { ASDCP::ATMOS::MXFReader reader; - auto r = reader.OpenRead (file.string().c_str()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); } diff --git a/src/atmos_asset_writer.cc b/src/atmos_asset_writer.cc index 9d7363d0..0c895b5b 100644 --- a/src/atmos_asset_writer.cc +++ b/src/atmos_asset_writer.cc @@ -43,6 +43,7 @@ #include "crypto_context.h" #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include @@ -94,7 +95,7 @@ AtmosAssetWriter::write (uint8_t const * data, int size) DCP_ASSERT (!_finalized); if (!_started) { - auto r = _state->mxf_writer.OpenWrite (_file.string().c_str(), _state->writer_info, _state->desc); + auto r = _state->mxf_writer.OpenWrite(dcp::filesystem::fix_long_path(_file).string().c_str(), _state->writer_info, _state->desc); if (ASDCP_FAILURE(r)) { boost::throw_exception (FileError ("could not open atmos MXF for writing", _file.string(), r)); } diff --git a/src/certificate_chain.cc b/src/certificate_chain.cc index 449dba89..6748d73c 100644 --- a/src/certificate_chain.cc +++ b/src/certificate_chain.cc @@ -41,6 +41,7 @@ #include "compose.hpp" #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include "util.h" #include "warnings.h" #include @@ -57,7 +58,6 @@ LIBDCP_ENABLE_WARNINGS #include #include #include -#include #include #include #include @@ -114,7 +114,7 @@ command (string cmd) int const code = WEXITSTATUS (r); #endif if (code) { - throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string())); + throw dcp::MiscError(String::compose("error %1 in %2 within %3", code, cmd, filesystem::current_path().string())); } } @@ -196,10 +196,14 @@ CertificateChain::CertificateChain ( ) { auto directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path (); - boost::filesystem::create_directories (directory); + filesystem::create_directories(directory); - auto const cwd = boost::filesystem::current_path (); - boost::filesystem::current_path (directory); + auto const cwd = boost::filesystem::current_path(); + /* On Windows we will use cmd.exe here, and that doesn't work with UNC paths, so make sure + * we don't use our own filesystem::current_path() as it will make the current working + * directory a UNC path. + */ + boost::filesystem::current_path(directory); string quoted_openssl = "\"" + openssl.string() + "\""; @@ -319,7 +323,10 @@ CertificateChain::CertificateChain ( ) ); - boost::filesystem::current_path (cwd); + /* Use boost:: rather than dcp:: here so we don't force UNC into the current path if it + * wasn't there before. + */ + boost::filesystem::current_path(cwd); _certificates.push_back (dcp::Certificate(dcp::file_to_string(directory / "ca.self-signed.pem"))); _certificates.push_back (dcp::Certificate(dcp::file_to_string(directory / "intermediate.signed.pem"))); @@ -327,7 +334,7 @@ CertificateChain::CertificateChain ( _key = dcp::file_to_string (directory / "leaf.key"); - boost::filesystem::remove_all (directory); + filesystem::remove_all(directory); } diff --git a/src/combine.cc b/src/combine.cc index b728298a..b7a625f0 100644 --- a/src/combine.cc +++ b/src/combine.cc @@ -43,6 +43,7 @@ #include "dcp.h" #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include "font_asset.h" #include "interop_subtitle_asset.h" #include "raw_convert.h" @@ -52,25 +53,25 @@ #include +using std::dynamic_pointer_cast; using std::map; using std::set; +using std::shared_ptr; using std::string; using std::vector; -using std::dynamic_pointer_cast; using boost::optional; -using std::shared_ptr; boost::filesystem::path make_unique (boost::filesystem::path path) { - if (!boost::filesystem::exists(path)) { + if (!dcp::filesystem::exists(path)) { return path; } for (int i = 0; i < 10000; ++i) { boost::filesystem::path p = path.parent_path() / (path.stem().string() + dcp::raw_convert(i) + path.extension().string()); - if (!boost::filesystem::exists(p)) { + if (!dcp::filesystem::exists(p)) { return p; } } @@ -85,10 +86,10 @@ void create_hard_link_or_copy (boost::filesystem::path from, boost::filesystem::path to) { try { - create_hard_link (from, to); + dcp::filesystem::create_hard_link(from, to); } catch (boost::filesystem::filesystem_error& e) { if (e.code() == boost::system::errc::cross_device_link) { - copy_file (from, to); + dcp::filesystem::copy_file(from, to); } else { throw; } @@ -107,8 +108,6 @@ dcp::combine ( shared_ptr signer ) { - using namespace boost::filesystem; - DCP_ASSERT (!inputs.empty()); DCP output_dcp (output); @@ -124,7 +123,7 @@ dcp::combine ( } } - vector paths; + vector paths; vector> assets; for (auto i: inputs) { @@ -153,7 +152,7 @@ dcp::combine ( } auto file = sub->file(); DCP_ASSERT (file); - path new_path = make_unique(output / file->filename()); + auto new_path = make_unique(output / file->filename()); sub->write (new_path); add_to_container(assets, sub->font_assets()); } @@ -168,7 +167,7 @@ dcp::combine ( if (!dynamic_pointer_cast(i) && !dynamic_pointer_cast(i)) { auto file = i->file(); DCP_ASSERT (file); - path new_path = make_unique(output / file->filename()); + auto new_path = make_unique(output / file->filename()); create_hard_link_or_copy (*file, new_path); i->set_file (new_path); } diff --git a/src/cpl.cc b/src/cpl.cc index bbdaee18..5467fef3 100644 --- a/src/cpl.cc +++ b/src/cpl.cc @@ -42,6 +42,7 @@ #include "cpl.h" #include "dcp_assert.h" #include "equality_options.h" +#include "filesystem.h" #include "local_time.h" #include "metadata.h" #include "raw_convert.h" @@ -108,7 +109,7 @@ CPL::CPL (boost::filesystem::path file) , _content_kind (ContentKind::FEATURE) { cxml::Document f ("CompositionPlaylist"); - f.read_file (file); + f.read_file(dcp::filesystem::fix_long_path(file)); if (f.namespace_uri() == cpl_interop_ns) { _standard = Standard::INTEROP; @@ -239,7 +240,7 @@ CPL::write_xml(boost::filesystem::path file, shared_ptr signer->sign (root, _standard); } - doc.write_to_file_formatted (file.string(), "UTF-8"); + doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8"); set_file (file); } diff --git a/src/dcp.cc b/src/dcp.cc index 7c253a17..d603cfae 100644 --- a/src/dcp.cc +++ b/src/dcp.cc @@ -47,6 +47,7 @@ #include "decrypted_kdm.h" #include "decrypted_kdm_key.h" #include "exceptions.h" +#include "filesystem.h" #include "font_asset.h" #include "interop_subtitle_asset.h" #include "metadata.h" @@ -71,7 +72,6 @@ LIBDCP_DISABLE_WARNINGS #include LIBDCP_ENABLE_WARNINGS #include -#include #include @@ -98,11 +98,11 @@ static string const volindex_smpte_ns = "http://www.smpte-ra.org/schemas/429-9 DCP::DCP (boost::filesystem::path directory) : _directory (directory) { - if (!boost::filesystem::exists (directory)) { - boost::filesystem::create_directories (directory); + if (!filesystem::exists(directory)) { + filesystem::create_directories(directory); } - _directory = boost::filesystem::canonical (_directory); + _directory = filesystem::canonical(_directory); } @@ -141,9 +141,9 @@ DCP::read (vector* notes, bool ignore_incorrect_picture_m /* Read the ASSETMAP and PKL */ boost::filesystem::path asset_map_path; - if (boost::filesystem::exists(_directory / "ASSETMAP")) { + if (filesystem::exists(_directory / "ASSETMAP")) { asset_map_path = _directory / "ASSETMAP"; - } else if (boost::filesystem::exists(_directory / "ASSETMAP.xml")) { + } else if (filesystem::exists(_directory / "ASSETMAP.xml")) { asset_map_path = _directory / "ASSETMAP.xml"; } else { boost::throw_exception(MissingAssetmapError(_directory)); @@ -189,7 +189,7 @@ DCP::read (vector* notes, bool ignore_incorrect_picture_m continue; } - if (!boost::filesystem::exists(path)) { + if (!filesystem::exists(path)) { if (notes) { notes->push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSET, path}); } @@ -224,7 +224,7 @@ DCP::read (vector* notes, bool ignore_incorrect_picture_m pkl_type == remove_parameters(InteropSubtitleAsset::static_pkl_type(standard))) { auto p = new xmlpp::DomParser; try { - p->parse_file (path.string()); + p->parse_file(dcp::filesystem::fix_long_path(path).string()); } catch (std::exception& e) { delete p; throw ReadError(String::compose("XML error in %1", path.string()), e.what()); @@ -442,7 +442,7 @@ DCP::write_volindex (Standard standard) const } root->add_child("Index")->add_child_text ("1"); - doc.write_to_file_formatted (p.string (), "UTF-8"); + doc.write_to_file_formatted(dcp::filesystem::fix_long_path(p).string(), "UTF-8"); } diff --git a/src/file.cc b/src/file.cc index b5d2eed4..36102f72 100644 --- a/src/file.cc +++ b/src/file.cc @@ -34,7 +34,7 @@ #include "dcp_assert.h" #include "file.h" -#include +#include "filesystem.h" #ifdef LIBDCP_WINDOWS #include #endif @@ -57,7 +57,7 @@ File::File(boost::filesystem::path path, std::string mode) SetLastError(0); std::wstring mode_wide(mode.begin(), mode.end()); /* c_str() here should give a UTF-16 string */ - _file = _wfopen(fix_long_path(path).c_str(), mode_wide.c_str()); + _file = _wfopen(dcp::filesystem::fix_long_path(path).c_str(), mode_wide.c_str()); if (!_file) { _open_error = GetLastError(); } @@ -216,45 +216,3 @@ File::error () DCP_ASSERT(_file); return ferror(_file); } - - -/** Windows can't "by default" cope with paths longer than 260 characters, so if you pass such a path to - * any boost::filesystem method it will fail. There is a "fix" for this, which is to prepend - * the string \\?\ to the path. This will make it work, so long as: - * - the path is absolute. - * - the path only uses backslashes. - * - individual path components are "short enough" (probably less than 255 characters) - * - * See https://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/reference.html under - * "Warning: Long paths on Windows" for some details. - * - * Our fopen_boost uses this method to get this fix, but any other calls to boost::filesystem - * will not unless this method is explicitly called to pre-process the pathname. - */ -boost::filesystem::path -dcp::fix_long_path (boost::filesystem::path long_path) -{ -#ifdef LIBDCP_WINDOWS - using namespace boost::filesystem; - - if (boost::algorithm::starts_with(long_path.string(), "\\\\")) { - /* This could mean it starts with \\ (i.e. a SMB path) or \\?\ (a long path) - * or a variety of other things... anyway, we'll leave it alone. - */ - return long_path; - } - - /* We have to make the path canonical but we can't call canonical() on the long path - * as it will fail. So we'll sort of do it ourselves (possibly badly). - */ - path fixed = "\\\\?\\"; - if (long_path.is_absolute()) { - fixed += long_path.make_preferred(); - } else { - fixed += boost::filesystem::current_path() / long_path.make_preferred(); - } - return fixed; -#else - return long_path; -#endif -} diff --git a/src/file.h b/src/file.h index 22083a9e..c530c26c 100644 --- a/src/file.h +++ b/src/file.h @@ -115,9 +115,6 @@ private: }; -boost::filesystem::path fix_long_path(boost::filesystem::path long_path); - - } diff --git a/src/filesystem.cc b/src/filesystem.cc new file mode 100644 index 00000000..0dd89228 --- /dev/null +++ b/src/filesystem.cc @@ -0,0 +1,329 @@ +/* + Copyright (C) 2012-2021 Carl Hetherington + + This file is part of libdcp. + + libdcp is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + libdcp is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include "filesystem.h" +#include + + +bool +dcp::filesystem::exists(boost::filesystem::path const& path) +{ + return boost::filesystem::exists(dcp::filesystem::fix_long_path(path)); +} + + +bool +dcp::filesystem::is_regular_file(boost::filesystem::path const& path) +{ + return boost::filesystem::is_regular_file(dcp::filesystem::fix_long_path(path)); +} + + +bool +dcp::filesystem::create_directory(boost::filesystem::path const& path) +{ + return boost::filesystem::create_directory(dcp::filesystem::fix_long_path(path)); +} + + +void +dcp::filesystem::copy(boost::filesystem::path const& from, boost::filesystem::path const& to) +{ + boost::filesystem::copy(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to)); +} + + +void +dcp::filesystem::copy_file(boost::filesystem::path const& from, boost::filesystem::path const& to) +{ + boost::filesystem::copy_file(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to)); +} + + +bool +dcp::filesystem::create_directories(boost::filesystem::path const& path) +{ + return boost::filesystem::create_directories(dcp::filesystem::fix_long_path(path)); +} + + +boost::filesystem::path +dcp::filesystem::absolute(boost::filesystem::path const& path) +{ + return dcp::filesystem::unfix_long_path(boost::filesystem::absolute(dcp::filesystem::fix_long_path(path))); +} + + +boost::filesystem::path +dcp::filesystem::canonical(boost::filesystem::path const& path) +{ + return dcp::filesystem::unfix_long_path(boost::filesystem::canonical(dcp::filesystem::fix_long_path(path))); +} + + +uintmax_t +dcp::filesystem::remove_all(boost::filesystem::path const& path) +{ + return boost::filesystem::remove_all(dcp::filesystem::fix_long_path(path)); +} + + +uintmax_t +dcp::filesystem::file_size(boost::filesystem::path const& path) +{ + return boost::filesystem::file_size(dcp::filesystem::fix_long_path(path)); +} + + +boost::filesystem::path +dcp::filesystem::current_path() +{ + return dcp::filesystem::unfix_long_path(boost::filesystem::current_path()); +} + + +void +dcp::filesystem::current_path(boost::filesystem::path const& path) +{ + boost::filesystem::current_path(dcp::filesystem::fix_long_path(path)); +} + + +void +dcp::filesystem::create_hard_link(boost::filesystem::path const& from, boost::filesystem::path const& to) +{ + boost::filesystem::create_hard_link(dcp::filesystem::fix_long_path(from), dcp::filesystem::fix_long_path(to)); +} + + +#ifdef DCPOMATIC_WINDOWS + +dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path) + : _wrapped(dcp::filesystem::fix_long_path(path)) +{ + +} + + +boost::filesystem::path +dcp::filesystem::directory_entry::path() const +{ + return dcp::filesystem::unfix_long_path(_path); +} + + +dcp::filesystem::directory_entry::operator boost::filesystem::path const &() const +{ + return dcp::filesystem::unfix_long_path(_path); +} + + +dcp::filesystem::recursive_directory_iterator::recursive_directory_iterator(boost::filesystem::path const& path) + : _wrapped(dcp::filesystem::fix_long_path(path)) +{ + +} + +#else + +dcp::filesystem::directory_iterator::directory_iterator(boost::filesystem::path const& path) + : _wrapped(path) +{ + +} + + +boost::filesystem::path +dcp::filesystem::directory_entry::path() const +{ + return _path; +} + + +dcp::filesystem::directory_entry::operator boost::filesystem::path const &() const +{ + return _path; +} + + +dcp::filesystem::recursive_directory_iterator::recursive_directory_iterator(boost::filesystem::path const& path) + : _wrapped(path) +{ + +} + +#endif + + +dcp::filesystem::directory_entry::directory_entry(boost::filesystem::path const& path) + : _path(path) +{ + +} + + +dcp::filesystem::directory_iterator& +dcp::filesystem::directory_iterator::operator++() +{ + ++_wrapped; + return *this; +} + + +dcp::filesystem::directory_entry +dcp::filesystem::directory_iterator::operator*() const +{ + return dcp::filesystem::directory_entry(*_wrapped); +} + + +bool +dcp::filesystem::directory_iterator::operator!=(dcp::filesystem::directory_iterator const& other) const +{ + return _wrapped != other._wrapped; +} + + +dcp::filesystem::directory_iterator const& +dcp::filesystem::begin(dcp::filesystem::directory_iterator const& iter) +{ + return iter; +} + + +dcp::filesystem::directory_iterator +dcp::filesystem::end(dcp::filesystem::directory_iterator const&) +{ + return dcp::filesystem::directory_iterator(); +} + + +dcp::filesystem::recursive_directory_iterator& +dcp::filesystem::recursive_directory_iterator::operator++() +{ + ++_wrapped; + return *this; +} + + +bool +dcp::filesystem::recursive_directory_iterator::operator!=(dcp::filesystem::recursive_directory_iterator const& other) const +{ + return _wrapped != other._wrapped; +} + + +dcp::filesystem::directory_entry +dcp::filesystem::recursive_directory_iterator::operator*() const +{ + _entry = dcp::filesystem::directory_entry(_wrapped->path()); + return _entry; +} + + +dcp::filesystem::directory_entry* +dcp::filesystem::recursive_directory_iterator::operator->() const +{ + _entry = dcp::filesystem::directory_entry(_wrapped->path()); + return &_entry; +} + + +dcp::filesystem::recursive_directory_iterator const& +dcp::filesystem::begin(dcp::filesystem::recursive_directory_iterator const& iter) +{ + return iter; +} + + +dcp::filesystem::recursive_directory_iterator +dcp::filesystem::end(dcp::filesystem::recursive_directory_iterator const&) +{ + return dcp::filesystem::recursive_directory_iterator(); +} + + +/** Windows can't "by default" cope with paths longer than 260 characters, so if you pass such a path to + * any boost::filesystem method it will fail. There is a "fix" for this, which is to prepend + * the string \\?\ to the path. This will make it work, so long as: + * - the path is absolute. + * - the path contains no .. parts. + * - the path only uses backslashes. + * - individual path components are "short enough" (probably less than 255 characters) + * + * See https://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/reference.html under + * "Warning: Long paths on Windows" for some details. + * + * Our fopen_boost uses this method to get this fix, but any other calls to boost::filesystem + * will not unless this method is explicitly called to pre-process the pathname. + */ +boost::filesystem::path +dcp::filesystem::fix_long_path(boost::filesystem::path long_path) +{ +#ifdef LIBDCP_WINDOWS + using namespace boost::filesystem; + + if (boost::algorithm::starts_with(long_path.string(), "\\\\")) { + /* This could mean it starts with \\ (i.e. a SMB path) or \\?\ (a long path) + * or a variety of other things... anyway, we'll leave it alone. + */ + return long_path; + } + + /* We have to make the path canonical but we can't call canonical() on the long path + * as it will fail. So we'll sort of do it ourselves (possibly badly). + */ + path fixed = "\\\\?\\"; + if (long_path.is_absolute()) { + fixed += long_path.make_preferred(); + } else { + fixed += filesystem::current_path() / long_path.make_preferred(); + } + return fixed; +#else + return long_path; +#endif +} + + +boost::filesystem::path +dcp::filesystem::unfix_long_path(boost::filesystem::path long_path) +{ +#ifdef LIBDCP_WINDOWS + if (boost::algorithm::starts_with(long_path.string(), "\\\\?\\")) { + return long_path.string().substr(4); + } +#endif + return long_path; +} diff --git a/src/filesystem.h b/src/filesystem.h new file mode 100644 index 00000000..d9fb2791 --- /dev/null +++ b/src/filesystem.h @@ -0,0 +1,124 @@ +/* + Copyright (C) 2012-2023 Carl Hetherington + + This file is part of libdcp. + + libdcp is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + libdcp is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#ifndef LIBDCP_FILESYSTEM_H +#define LIBDCP_FILESYSTEM_H + + +#include + + +namespace dcp +{ +namespace filesystem +{ + +boost::filesystem::path absolute(boost::filesystem::path const& path); +boost::filesystem::path canonical(boost::filesystem::path const& path); +void copy(boost::filesystem::path const& from, boost::filesystem::path const& to); +void copy_file(boost::filesystem::path const& from, boost::filesystem::path const& to); +bool create_directory(boost::filesystem::path const& path); +bool create_directories(boost::filesystem::path const& path); +void create_hard_link(boost::filesystem::path const& from, boost::filesystem::path const& to); +void current_path(boost::filesystem::path const& path); +boost::filesystem::path current_path(); +bool exists(boost::filesystem::path const& path); +bool is_regular_file(boost::filesystem::path const& path); +uintmax_t file_size(boost::filesystem::path const& path); +uintmax_t remove_all(boost::filesystem::path const& path); + + +class directory_entry +{ +public: + directory_entry() {} + directory_entry(boost::filesystem::path const& path); + + boost::filesystem::path path() const; + operator boost::filesystem::path const&() const; + +private: + boost::filesystem::path _path; +}; + + +class directory_iterator +{ +public: + directory_iterator() = default; + directory_iterator(boost::filesystem::path const& path); + + directory_iterator& operator++(); + directory_entry operator*() const; + bool operator!=(directory_iterator const& other) const; + +private: + boost::filesystem::directory_iterator _wrapped; +}; + + +directory_iterator const& begin(directory_iterator const& iter); +directory_iterator end(directory_iterator const&); + + + +class recursive_directory_iterator +{ +public: + recursive_directory_iterator() = default; + recursive_directory_iterator(boost::filesystem::path const& path); + + recursive_directory_iterator& operator++(); + directory_entry operator*() const; + directory_entry* operator->() const; + bool operator!=(recursive_directory_iterator const& other) const; + +private: + boost::filesystem::recursive_directory_iterator _wrapped; + mutable directory_entry _entry; +}; + + +recursive_directory_iterator const& begin(recursive_directory_iterator const& iter); +recursive_directory_iterator end(recursive_directory_iterator const&); + + +boost::filesystem::path fix_long_path(boost::filesystem::path long_path); +boost::filesystem::path unfix_long_path(boost::filesystem::path long_path); + +} +} + + +#endif diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc index d26143a4..32c3f66a 100644 --- a/src/interop_subtitle_asset.cc +++ b/src/interop_subtitle_asset.cc @@ -40,6 +40,7 @@ #include "compose.hpp" #include "dcp_assert.h" #include "equality_options.h" +#include "filesystem.h" #include "font_asset.h" #include "file.h" #include "interop_load_font_node.h" @@ -75,7 +76,7 @@ InteropSubtitleAsset::InteropSubtitleAsset (boost::filesystem::path file) _raw_xml = dcp::file_to_string (file); auto xml = make_shared("DCSubtitle"); - xml->read_file (file); + xml->read_file(dcp::filesystem::fix_long_path(file)); _id = xml->string_child ("SubtitleID"); _reel_number = xml->string_child ("ReelNumber"); _language = xml->string_child ("Language"); diff --git a/src/mono_picture_asset.cc b/src/mono_picture_asset.cc index 55d3f6ba..cc24e198 100644 --- a/src/mono_picture_asset.cc +++ b/src/mono_picture_asset.cc @@ -41,6 +41,7 @@ #include "dcp_assert.h" #include "equality_options.h" #include "exceptions.h" +#include "filesystem.h" #include "mono_picture_asset.h" #include "mono_picture_asset_reader.h" #include "mono_picture_asset_writer.h" @@ -66,7 +67,7 @@ MonoPictureAsset::MonoPictureAsset (boost::filesystem::path file) : PictureAsset (file) { ASDCP::JP2K::MXFReader reader; - auto r = reader.OpenRead (file.string().c_str()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); } @@ -110,14 +111,14 @@ MonoPictureAsset::equals(shared_ptr other, EqualityOptions const& o ASDCP::JP2K::MXFReader reader_A; DCP_ASSERT (_file); - auto r = reader_A.OpenRead (_file->string().c_str()); + auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", _file->string(), r)); } ASDCP::JP2K::MXFReader reader_B; DCP_ASSERT (other->file ()); - r = reader_B.OpenRead (other->file()->string().c_str()); + r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not open MXF file for reading", other->file()->string(), r)); } diff --git a/src/mono_picture_frame.cc b/src/mono_picture_frame.cc index ddaed983..2abd57e4 100644 --- a/src/mono_picture_frame.cc +++ b/src/mono_picture_frame.cc @@ -42,6 +42,7 @@ #include "crypto_context.h" #include "exceptions.h" #include "file.h" +#include "filesystem.h" #include "j2k_transcode.h" #include "mono_picture_frame.h" #include "rgb_xyz.h" @@ -59,7 +60,7 @@ using namespace dcp; MonoPictureFrame::MonoPictureFrame (boost::filesystem::path path) { - auto const size = boost::filesystem::file_size (path); + auto const size = filesystem::file_size(path); _buffer.reset(new ASDCP::JP2K::FrameBuffer(size)); File f(path, "rb"); if (!f) { diff --git a/src/picture_asset_writer_common.cc b/src/picture_asset_writer_common.cc index e719be72..82866aac 100644 --- a/src/picture_asset_writer_common.cc +++ b/src/picture_asset_writer_common.cc @@ -37,6 +37,9 @@ */ +#include "filesystem.h" + + using std::shared_ptr; @@ -76,8 +79,8 @@ void dcp::start (PictureAssetWriter* writer, shared_ptr

state, Q* asset, uint asset->fill_writer_info (&state->writer_info, asset->id()); - auto r = state->mxf_writer.OpenWrite ( - asset->file()->string().c_str(), + auto r = state->mxf_writer.OpenWrite( + dcp::filesystem::fix_long_path(*asset->file()).string().c_str(), state->writer_info, state->picture_descriptor, 16384, diff --git a/src/pkl.cc b/src/pkl.cc index 390d5df2..57eda9da 100644 --- a/src/pkl.cc +++ b/src/pkl.cc @@ -39,6 +39,7 @@ #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include "pkl.h" #include "raw_convert.h" #include "util.h" @@ -64,7 +65,7 @@ PKL::PKL (boost::filesystem::path file) : _file (file) { cxml::Document pkl ("PackingList"); - pkl.read_file (file); + pkl.read_file(dcp::filesystem::fix_long_path(file)); if (pkl.namespace_uri() == pkl_interop_ns) { _standard = Standard::INTEROP; @@ -133,7 +134,7 @@ PKL::write_xml (boost::filesystem::path file, shared_ptr signer->sign (pkl, _standard); } - doc.write_to_file_formatted (file.string(), "UTF-8"); + doc.write_to_file_formatted(dcp::filesystem::fix_long_path(file).string(), "UTF-8"); _file = file; } diff --git a/src/search.cc b/src/search.cc index f45a2fb9..e7871dc6 100644 --- a/src/search.cc +++ b/src/search.cc @@ -35,6 +35,7 @@ #include "dcp.h" #include "decrypted_kdm.h" #include "exceptions.h" +#include "filesystem.h" #include "search.h" @@ -59,7 +60,7 @@ dcp::find_and_resolve_cpls (vector const& directories, vector> dcps; for (auto i: directories) { - if (!boost::filesystem::exists(i)) { + if (!filesystem::exists(i)) { /* Don't make a DCP object or it will try to create the parent directories * of i if they do not exist (#2344). */ diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index b9d7e9a3..ea1d7426 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -42,6 +42,7 @@ #include "dcp_assert.h" #include "equality_options.h" #include "exceptions.h" +#include "filesystem.h" #include "raw_convert.h" #include "smpte_load_font_node.h" #include "smpte_subtitle_asset.h" @@ -98,7 +99,7 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) auto r = Kumu::RESULT_OK; { ASDCPErrorSuspender sus; - r = reader->OpenRead (_file->string().c_str ()); + r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); } if (!ASDCP_FAILURE(r)) { /* MXF-wrapped */ @@ -122,7 +123,7 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) try { _raw_xml = dcp::file_to_string (file); xml = make_shared("SubtitleReel"); - xml->read_file (file); + xml->read_file(dcp::filesystem::fix_long_path(file)); parse_xml (xml); } catch (cxml::Error& e) { boost::throw_exception ( @@ -143,11 +144,11 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) if (im && im->png_image().size() == 0) { /* Even more dubious; allow .png or urn:uuid:.png */ auto p = file.parent_path() / String::compose("%1.png", im->id()); - if (boost::filesystem::is_regular_file(p)) { + if (filesystem::is_regular_file(p)) { im->read_png_file (p); } else if (starts_with (im->id(), "urn:uuid:")) { p = file.parent_path() / String::compose("%1.png", remove_urn_uuid(im->id())); - if (boost::filesystem::is_regular_file(p)) { + if (filesystem::is_regular_file(p)) { im->read_png_file (p); } } @@ -319,7 +320,7 @@ SMPTESubtitleAsset::set_key (Key key) /* Our data was encrypted; now we can decrypt it */ auto reader = make_shared(); - auto r = reader->OpenRead (_file->string().c_str ()); + auto r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception ( ReadError ( @@ -353,7 +354,7 @@ SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file) { ASDCP::TimedText::MXFReader reader; Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL); - auto r = reader.OpenRead (file.string().c_str ()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL); return !ASDCP_FAILURE (r); } @@ -450,7 +451,7 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const /* This header size is a guess. Empirically it seems that each subtitle reference is 90 bytes, and we need some extra. The default size is not enough for some feature-length PNG sub projects (see DCP-o-matic #1561). */ - ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor, _subtitles.size() * 90 + 16384); + ASDCP::Result_t r = writer.OpenWrite(dcp::filesystem::fix_long_path(p).string().c_str(), writer_info, descriptor, _subtitles.size() * 90 + 16384); if (ASDCP_FAILURE (r)) { boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r)); } diff --git a/src/sound_asset.cc b/src/sound_asset.cc index c31f8bf4..0ceba53d 100644 --- a/src/sound_asset.cc +++ b/src/sound_asset.cc @@ -41,6 +41,7 @@ #include "dcp_assert.h" #include "equality_options.h" #include "exceptions.h" +#include "filesystem.h" #include "sound_asset.h" #include "sound_asset_reader.h" #include "sound_asset_writer.h" @@ -70,7 +71,7 @@ SoundAsset::SoundAsset (boost::filesystem::path file) : Asset (file) { ASDCP::PCM::MXFReader reader; - auto r = reader.OpenRead (file.string().c_str()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); } @@ -139,13 +140,13 @@ SoundAsset::equals(shared_ptr other, EqualityOptions const& opt, No ASDCP::PCM::MXFReader reader_A; DCP_ASSERT (file()); - auto r = reader_A.OpenRead (file()->string().c_str()); + auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*file()).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r)); } ASDCP::PCM::MXFReader reader_B; - r = reader_B.OpenRead (other->file()->string().c_str()); + r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r)); } @@ -278,7 +279,7 @@ bool SoundAsset::valid_mxf (boost::filesystem::path file) { ASDCP::PCM::MXFReader reader; - Kumu::Result_t r = reader.OpenRead (file.string().c_str()); + Kumu::Result_t r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); return !ASDCP_FAILURE (r); } diff --git a/src/sound_asset_writer.cc b/src/sound_asset_writer.cc index 1185616a..a7be0859 100644 --- a/src/sound_asset_writer.cc +++ b/src/sound_asset_writer.cc @@ -42,6 +42,7 @@ #include "crypto_context.h" #include "dcp_assert.h" #include "exceptions.h" +#include "filesystem.h" #include "sound_asset.h" #include "sound_asset_writer.h" #include "warnings.h" @@ -146,7 +147,7 @@ SoundAssetWriter::~SoundAssetWriter() void SoundAssetWriter::start () { - auto r = _state->mxf_writer.OpenWrite (_file.string().c_str(), _state->writer_info, _state->desc); + auto r = _state->mxf_writer.OpenWrite(dcp::filesystem::fix_long_path(_file).string().c_str(), _state->writer_info, _state->desc); if (ASDCP_FAILURE(r)) { boost::throw_exception (FileError("could not open audio MXF for writing", _file.string(), r)); } diff --git a/src/stereo_picture_asset.cc b/src/stereo_picture_asset.cc index 3fec58ff..2ce3cdc9 100644 --- a/src/stereo_picture_asset.cc +++ b/src/stereo_picture_asset.cc @@ -40,6 +40,7 @@ #include "dcp_assert.h" #include "equality_options.h" #include "exceptions.h" +#include "filesystem.h" #include "stereo_picture_asset.h" #include "stereo_picture_asset_reader.h" #include "stereo_picture_asset_writer.h" @@ -59,7 +60,7 @@ StereoPictureAsset::StereoPictureAsset (boost::filesystem::path file) : PictureAsset (file) { ASDCP::JP2K::MXFSReader reader; - auto r = reader.OpenRead (file.string().c_str()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); } @@ -106,14 +107,14 @@ StereoPictureAsset::equals(shared_ptr other, EqualityOptions const& { ASDCP::JP2K::MXFSReader reader_A; DCP_ASSERT (file()); - auto r = reader_A.OpenRead (file()->string().c_str()); + auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not open MXF file for reading", file()->string(), r)); } ASDCP::JP2K::MXFSReader reader_B; DCP_ASSERT (other->file()); - r = reader_B.OpenRead (other->file()->string().c_str()); + r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not open MXF file for reading", other->file()->string(), r)); } diff --git a/src/subtitle_image.cc b/src/subtitle_image.cc index 1ef2e697..9340bc54 100644 --- a/src/subtitle_image.cc +++ b/src/subtitle_image.cc @@ -38,6 +38,7 @@ #include "equality_options.h" +#include "filesystem.h" #include "subtitle_image.h" #include "util.h" @@ -149,12 +150,12 @@ SubtitleImage::equals(shared_ptr other_sub, EqualityOptions cons note (NoteType::ERROR, "subtitle image PNG data differs"); if (options.export_differing_subtitles) { string const base = "dcpdiff_subtitle_"; - if (boost::filesystem::exists(base + "A.png")) { + if (filesystem::exists(base + "A.png")) { note (NoteType::ERROR, "could not export subtitle as " + base + "A.png already exists"); } else { png_image().write(base + "A.png"); } - if (boost::filesystem::exists(base + "B.png")) { + if (filesystem::exists(base + "B.png")) { note (NoteType::ERROR, "could not export subtitle as " + base + "B.png already exists"); } else { other->png_image().write(base + "B.png"); diff --git a/src/util.cc b/src/util.cc index bbfcd30e..1ff36f59 100644 --- a/src/util.cc +++ b/src/util.cc @@ -42,6 +42,7 @@ #include "dcp_assert.h" #include "exceptions.h" #include "file.h" +#include "filesystem.h" #include "language_tag.h" #include "openjpeg_image.h" #include "rating.h" @@ -119,7 +120,7 @@ string dcp::make_digest (boost::filesystem::path filename, function progress) { Kumu::FileReader reader; - auto r = reader.OpenRead (filename.string().c_str ()); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(filename).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (FileError("could not open file to compute digest", filename, r)); } @@ -254,7 +255,7 @@ dcp::ids_equal (string a, string b) string dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length) { - auto len = boost::filesystem::file_size (p); + auto len = filesystem::file_size(p); if (len > max_length) { throw MiscError (String::compose("Unexpectedly long file (%1)", p.string())); } @@ -421,7 +422,7 @@ ASDCPErrorSuspender::~ASDCPErrorSuspender () boost::filesystem::path dcp::directory_containing_executable () { #if BOOST_VERSION >= 106100 - return boost::filesystem::canonical(boost::dll::program_location().parent_path()); + return filesystem::canonical(boost::dll::program_location().parent_path()); #else char buffer[PATH_MAX]; ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX); diff --git a/src/verify.cc b/src/verify.cc index 620ee31d..dba5dfb1 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -41,6 +41,7 @@ #include "cpl.h" #include "dcp.h" #include "exceptions.h" +#include "filesystem.h" #include "interop_subtitle_asset.h" #include "mono_picture_asset.h" #include "mono_picture_frame.h" @@ -504,7 +505,7 @@ verify_main_picture_asset ( auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || boost::filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { + if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { stage ("Checking picture asset hash", file); auto const r = verify_asset (dcp, reel_asset, progress); switch (r) { @@ -600,7 +601,7 @@ verify_main_sound_asset ( auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || boost::filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { + if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { stage("Checking sound asset hash", file); auto const r = verify_asset (dcp, reel_asset, progress); switch (r) { @@ -678,7 +679,7 @@ verify_smpte_timed_text_asset ( notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file() }); } - auto const size = boost::filesystem::file_size(asset->file().get()); + auto const size = filesystem::file_size(asset->file().get()); if (size > 115 * 1024 * 1024) { notes.push_back ( { VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, raw_convert(size), *asset->file() } @@ -1297,7 +1298,7 @@ verify_extension_metadata(shared_ptr cpl, vector& n { DCP_ASSERT (cpl->file()); cxml::Document doc ("CompositionPlaylist"); - doc.read_file (cpl->file().get()); + doc.read_file(dcp::filesystem::fix_long_path(cpl->file().get())); auto missing = false; string malformed; @@ -1739,7 +1740,7 @@ verify_cpl( if (cpl->any_encrypted()) { cxml::Document doc("CompositionPlaylist"); DCP_ASSERT(cpl->file()); - doc.read_file(cpl->file().get()); + doc.read_file(dcp::filesystem::fix_long_path(cpl->file().get())); if (!doc.optional_node_child("Signature")) { notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), cpl->file().get()}); } @@ -1761,7 +1762,7 @@ verify_pkl( if (pkl_has_encrypted_assets(dcp, pkl)) { cxml::Document doc("PackingList"); - doc.read_file(pkl->file().get()); + doc.read_file(dcp::filesystem::fix_long_path(pkl->file().get())); if (!doc.optional_node_child("Signature")) { notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl->id(), pkl->file().get()}); } @@ -1813,7 +1814,7 @@ dcp::verify ( if (!xsd_dtd_directory) { xsd_dtd_directory = resources_directory() / "xsd"; } - *xsd_dtd_directory = boost::filesystem::canonical (*xsd_dtd_directory); + *xsd_dtd_directory = filesystem::canonical(*xsd_dtd_directory); vector notes; State state{}; diff --git a/src/wscript b/src/wscript index a408d7cd..e6bee6f2 100644 --- a/src/wscript +++ b/src/wscript @@ -57,6 +57,7 @@ def build(bld): encrypted_kdm.cc exceptions.cc file.cc + filesystem.cc font_asset.cc fsk.cc gamma_transfer_function.cc @@ -156,6 +157,7 @@ def build(bld): equality_options.h exceptions.h file.h + filesystem.h font_asset.h frame.h fsk.h diff --git a/test/encryption_test.cc b/test/encryption_test.cc index f2a40843..358a3fd9 100644 --- a/test/encryption_test.cc +++ b/test/encryption_test.cc @@ -36,6 +36,7 @@ #include "dcp.h" #include "certificate_chain.h" #include "cpl.h" +#include "filesystem.h" #include "mono_picture_asset.h" #include "picture_asset_writer.h" #include "sound_asset_writer.h" @@ -155,6 +156,11 @@ BOOST_AUTO_TEST_CASE (encryption_test) kdm.encrypt (signer, signer->leaf(), vector(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, 0).as_xml("build/test/encryption_test.kdm.xml"); + /* Make sure we aren't in a UNC current working directory otherwise the use of cmd.exe + * in system() below will fail. + */ + boost::filesystem::current_path(dcp::filesystem::unfix_long_path(boost::filesystem::current_path())); + int r = system ( "xmllint --path schema --nonet --noout --schema schema/SMPTE-430-1-2006-Amd-1-2009-KDM.xsd build/test/encryption_test.kdm.xml " #ifndef LIBDCP_WINDOWS diff --git a/test/file_test.cc b/test/filesystem_test.cc similarity index 63% rename from test/file_test.cc rename to test/filesystem_test.cc index 8c6ded29..fc13db99 100644 --- a/test/file_test.cc +++ b/test/filesystem_test.cc @@ -33,6 +33,7 @@ #include "file.h" +#include "filesystem.h" #include #include @@ -40,16 +41,23 @@ BOOST_AUTO_TEST_CASE (fix_long_path_test) { #ifdef LIBDCP_WINDOWS - BOOST_CHECK_EQUAL (dcp::fix_long_path("c:\\foo"), "\\\\?\\c:\\foo"); - BOOST_CHECK_EQUAL (dcp::fix_long_path("c:\\foo\\bar"), "\\\\?\\c:\\foo\\bar"); - boost::filesystem::path fixed_bar = "\\\\?\\"; - fixed_bar += boost::filesystem::current_path(); - fixed_bar /= "bar"; - BOOST_CHECK_EQUAL (dcp::fix_long_path("bar"), fixed_bar); - - BOOST_CHECK_EQUAL (dcp::fix_long_path("\\\\?\\c:\\foo"), "\\\\?\\c:\\foo"); + BOOST_CHECK_EQUAL(dcp::filesystem::fix_long_path("c:\\foo"), "\\\\?\\c:\\foo"); + BOOST_CHECK_EQUAL(dcp::filesystem::fix_long_path("c:\\foo\\bar"), "\\\\?\\c:\\foo\\bar"); + BOOST_CHECK_EQUAL(dcp::filesystem::fix_long_path("\\\\?\\c:\\foo"), "\\\\?\\c:\\foo"); #else - BOOST_CHECK_EQUAL (dcp::fix_long_path("foo/bar/baz"), "foo/bar/baz"); + BOOST_CHECK_EQUAL(dcp::filesystem::fix_long_path("foo/bar/baz"), "foo/bar/baz"); +#endif +} + + +BOOST_AUTO_TEST_CASE(unfix_long_path_test) +{ +#ifdef LIBDCP_WINDOWS + BOOST_CHECK_EQUAL(dcp::filesystem::unfix_long_path("c:\\foo"), "c:\\foo"); + BOOST_CHECK_EQUAL(dcp::filesystem::unfix_long_path("\\\\?\\c:\\foo"), "c:\\foo"); +#else + BOOST_CHECK_EQUAL(dcp::filesystem::unfix_long_path("c:\\foo"), "c:\\foo"); + BOOST_CHECK_EQUAL(dcp::filesystem::unfix_long_path("\\\\?\\c:\\foo"), "\\\\?\\c:\\foo"); #endif } @@ -59,14 +67,15 @@ BOOST_AUTO_TEST_CASE (windows_long_filename_test) { using namespace boost::filesystem; - path too_long = current_path() / "build\\test\\a\\really\\very\\long\\filesystem\\path\\indeed\\that\\will\\be\\so\\long\\that\\windows\\cannot\\normally\\cope\\with\\it\\unless\\we\\add\\this\\crazy\\prefix\\and\\then\\magically\\it\\can\\do\\it\\fine\\I\\dont\\really\\know\\why\\its\\like\\that\\but\\hey\\it\\is\\so\\here\\we\\are\\what\\can\\we\\do\\other\\than\\bodge\\it"; + /* Make sure current_path() is not already fixed by using our dcp::filesystem version */ + path too_long = dcp::filesystem::current_path() / "build\\test\\a\\really\\very\\long\\filesystem\\path\\indeed\\that\\will\\be\\so\\long\\that\\windows\\cannot\\normally\\cope\\with\\it\\unless\\we\\add\\this\\crazy\\prefix\\and\\then\\magically\\it\\can\\do\\it\\fine\\I\\dont\\really\\know\\why\\its\\like\\that\\but\\hey\\it\\is\\so\\here\\we\\are\\what\\can\\we\\do\\other\\than\\bodge\\it"; BOOST_CHECK (too_long.string().length() > 260); boost::system::error_code ec; create_directories (too_long, ec); BOOST_CHECK (ec); - path fixed_path = dcp::fix_long_path(too_long); + path fixed_path = dcp::filesystem::fix_long_path(too_long); create_directories (fixed_path, ec); BOOST_CHECK (!ec); diff --git a/test/long_filenames_test.cc b/test/long_filenames_test.cc new file mode 100644 index 00000000..f8189884 --- /dev/null +++ b/test/long_filenames_test.cc @@ -0,0 +1,59 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of libdcp. + + libdcp is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + libdcp is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include "dcp.h" +#include "filesystem.h" +#include +#include + + +BOOST_AUTO_TEST_CASE(load_dcp_with_long_filename) +{ + boost::filesystem::path long_name = "build/test"; + for (int i = 0; i < 27; ++i) { + long_name /= "letsmakeitlong"; + } + + dcp::filesystem::remove_all(long_name); + dcp::filesystem::create_directories(long_name); + for (auto file: dcp::filesystem::directory_iterator("test/ref/DCP/dcp_test1")) { + dcp::filesystem::copy(file.path(), long_name / file.path().filename()); + } + + dcp::DCP dcp(long_name); + BOOST_CHECK_NO_THROW(dcp.read()); +} + + + diff --git a/test/wscript b/test/wscript index bc786322..4d9278eb 100644 --- a/test/wscript +++ b/test/wscript @@ -82,7 +82,7 @@ def build(bld): effect_test.cc encryption_test.cc exception_test.cc - file_test.cc + filesystem_test.cc fraction_test.cc frame_info_hash_test.cc gamma_transfer_function_test.cc @@ -90,6 +90,7 @@ def build(bld): interop_load_font_test.cc interop_subtitle_test.cc local_time_test.cc + long_filenames_test.cc make_digest_test.cc markers_test.cc mca_test.cc diff --git a/tools/dcpdiff.cc b/tools/dcpdiff.cc index 3dace12a..7c7c4b71 100644 --- a/tools/dcpdiff.cc +++ b/tools/dcpdiff.cc @@ -32,10 +32,11 @@ */ +#include "common.h" #include "dcp.h" #include "equality_options.h" #include "exceptions.h" -#include "common.h" +#include "filesystem.h" #include "mxf.h" #include #include @@ -207,12 +208,12 @@ main (int argc, char* argv[]) exit (EXIT_FAILURE); } - if (!boost::filesystem::exists (argv[optind])) { + if (!filesystem::exists(argv[optind])) { cerr << argv[0] << ": DCP " << argv[optind] << " not found.\n"; exit (EXIT_FAILURE); } - if (!boost::filesystem::exists (argv[optind + 1])) { + if (!filesystem::exists(argv[optind + 1])) { cerr << argv[0] << ": DCP " << argv[optind + 1] << " not found.\n"; exit (EXIT_FAILURE); } diff --git a/tools/dcpinfo.cc b/tools/dcpinfo.cc index 6a37be1c..e812afe4 100644 --- a/tools/dcpinfo.cc +++ b/tools/dcpinfo.cc @@ -39,6 +39,7 @@ #include "decrypted_kdm.h" #include "encrypted_kdm.h" #include "exceptions.h" +#include "filesystem.h" #include "interop_subtitle_asset.h" #include "mono_picture_asset.h" #include "picture_asset.h" @@ -352,7 +353,7 @@ main (int argc, char* argv[]) exit (EXIT_FAILURE); } - if (!boost::filesystem::exists (argv[optind])) { + if (!filesystem::exists(argv[optind])) { cerr << argv[0] << ": DCP or CPL " << argv[optind] << " not found.\n"; exit (EXIT_FAILURE); } diff --git a/tools/dcprecover.cc b/tools/dcprecover.cc index 840a6711..b78846ff 100644 --- a/tools/dcprecover.cc +++ b/tools/dcprecover.cc @@ -36,6 +36,7 @@ #include "cpl.h" #include "dcp.h" #include "exceptions.h" +#include "filesystem.h" #include "reel_asset.h" #include "warnings.h" #include @@ -124,7 +125,7 @@ main (int argc, char* argv[]) /* Look for a CPL */ shared_ptr cpl; - for (auto i: boost::filesystem::directory_iterator(dcp_dir)) { + for (auto i: dcp::filesystem::directory_iterator(dcp_dir)) { if (i.path().extension() == ".xml") { try { cpl = make_shared(i.path()); @@ -146,7 +147,7 @@ main (int argc, char* argv[]) /* Read all MXF assets */ vector> assets; - for (auto i: boost::filesystem::directory_iterator(dcp_dir)) { + for (auto i: dcp::filesystem::directory_iterator(dcp_dir)) { if (i.path().extension() == ".mxf") { try { auto asset = dcp::asset_factory(i.path(), true); diff --git a/tools/dcpverify.cc b/tools/dcpverify.cc index 770dd78f..875a92b7 100644 --- a/tools/dcpverify.cc +++ b/tools/dcpverify.cc @@ -34,6 +34,7 @@ #include "common.h" #include "compose.hpp" +#include "filesystem.h" #include "raw_convert.h" #include "verify.h" #include "version.h" @@ -133,7 +134,7 @@ main (int argc, char* argv[]) exit (EXIT_FAILURE); } - if (!boost::filesystem::exists (argv[optind])) { + if (!dcp::filesystem::exists(argv[optind])) { cerr << argv[0] << ": DCP " << argv[optind] << " not found.\n"; exit (EXIT_FAILURE); } -- 2.30.2