From baea48fbec911f8c2953be35697f6bab5d05eaea Mon Sep 17 00:00:00 2001
From: Alexander Boettcher <alexander.boettcher@genode-labs.com>
Date: Wed, 9 Feb 2022 09:27:09 +0100
Subject: [PATCH] iso9660: move to genode-world

Fixes #4413
---
 doc/components.txt                     |   4 -
 repos/os/src/server/iso9660/README     |  27 --
 repos/os/src/server/iso9660/iso9660.cc | 476 -------------------------
 repos/os/src/server/iso9660/iso9660.h  |  89 -----
 repos/os/src/server/iso9660/main.cc    | 197 ----------
 repos/os/src/server/iso9660/target.mk  |   5 -
 6 files changed, 798 deletions(-)
 delete mode 100644 repos/os/src/server/iso9660/README
 delete mode 100644 repos/os/src/server/iso9660/iso9660.cc
 delete mode 100644 repos/os/src/server/iso9660/iso9660.h
 delete mode 100644 repos/os/src/server/iso9660/main.cc
 delete mode 100644 repos/os/src/server/iso9660/target.mk

diff --git a/doc/components.txt b/doc/components.txt
index b676abe2fc..b0c1b44b7a 100644
--- a/doc/components.txt
+++ b/doc/components.txt
@@ -343,10 +343,6 @@ Separate components
   Provides each file contained in a tar file obtained via Genode's ROM session
   as separate ROM session.
 
-:_os/src/server/iso9660/_:
-  Provides each file of an ISO9660 file system accessed via a block session as
-  separate ROM session.
-
 :_os/src/server/lx_fs/_:
   A file system server that makes the file system of a Linux base platform
   available to Genode.
diff --git a/repos/os/src/server/iso9660/README b/repos/os/src/server/iso9660/README
deleted file mode 100644
index 9ac812303c..0000000000
--- a/repos/os/src/server/iso9660/README
+++ /dev/null
@@ -1,27 +0,0 @@
-This directory contains an implementation of an ISO 9660 file system.
-
-Limitations
------------
-
-At the moment, the only file-name format supported is the Rock Ridge extension.
-The ISO specified 8.3 upper-case-file names are not supported, as well as Joliet.
-
-Usage
------
-
-The server requires an ATAPI-block device as back-end. Please have a look at
-'os/src/drivers/atapi'. The front-end of the server is implemented as a ROM
-session server. In order to access this server from your application, you need
-to route the ROM session to the ISO-ROM-session server in Genode's configuration
-file:
-
-!<start name="test-iso">
-!  <resource name="RAM" quantum="10M" />
-!    <route>
-!      <service name="ROM"><child name="iso9660"/></service>
-!    </route>
-!</start>
-
-Currently, the RAM quota necessary to obtain a file from the ISO file system
-is allocated on behalf of the ISO server. Please make sure to provide
-sufficient RAM quota to the ISO server.
diff --git a/repos/os/src/server/iso9660/iso9660.cc b/repos/os/src/server/iso9660/iso9660.cc
deleted file mode 100644
index 768101e230..0000000000
--- a/repos/os/src/server/iso9660/iso9660.cc
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * \brief  ISO 9660 file system support
- * \author Sebastian Sumpf <sebastian.sumpf@genode-labs.com>
- * \date   2010-07-26
- */
-
-/*
- * Copyright (C) 2010-2017 Genode Labs GmbH
- *
- * This file is part of the Genode OS framework, which is distributed
- * under the terms of the GNU Affero General Public License version 3.
- */
-
-/* Genode includes */
-#include <base/allocator_avl.h>
-#include <base/exception.h>
-#include <base/log.h>
-#include <base/stdint.h>
-#include <util/misc_math.h>
-#include <util/token.h>
-
-#include "iso9660.h"
-
-using namespace Genode;
-
-namespace Iso {
-	class Sector;
-	class Rock_ridge;
-	class Iso_base;
-}
-
-
-/*
- *  Sector reads one or more packets from the block interface
- */
-class Iso::Sector {
-
-	public:
-
-		enum {
-			MAX_SECTORS = 32, /* max. number sectors that can be read in one
-			                     transaction */
-			BLOCK_SIZE = 2048,
-		};
-
-	private:
-
-		Block::Session::Tx::Source &_source;
-		Block::Packet_descriptor    _p { };
-
-	public:
-
-		Sector(Block::Connection<> &block,
-		       unsigned long blk_nr, unsigned long count)
-		: _source(*block.tx())
-		{
-			try {
-				_p = Block::Packet_descriptor(
-					block.alloc_packet(blk_size() * count),
-					Block::Packet_descriptor::READ,
-					blk_nr * ((float)blk_size() / BLOCK_SIZE),
-					count * ((float)blk_size() / BLOCK_SIZE));
-
-				_source.submit_packet(_p);
-				_p = _source.get_acked_packet();
-
-				if (!_p.succeeded()) {
-					Genode::error("Could not read block ", blk_nr);
-					throw Io_error();
-				}
-
-			} catch (Block::Session::Tx::Source::Packet_alloc_failed) {
-				Genode::error("packet overrun!");
-				_p = _source.get_acked_packet();
-				throw Io_error();
-			}
-		}
-
-		~Sector() {
-			_source.release_packet(_p);
-		}
-
-		/**
-		 * Return address of packet content
-		 */
-		template <typename T>
-		T addr() { return reinterpret_cast<T>(_source.packet_content(_p)); }
-
-		static size_t blk_size() { return BLOCK_SIZE; }
-
-		static unsigned long to_blk(unsigned long bytes) {
-			return ((bytes + blk_size() - 1) & ~(blk_size() - 1)) / blk_size(); }
-};
-
-
-/**
- * Rock ridge extension (see IEEE P1282)
- */
-class Iso::Rock_ridge
-{
-	public:
-
-		enum {
-			NM = 0x4d4e, /* POSIX name system use entry (little endian) */
-		};
-
-	private:
-
-		uint16_t _signature;
-		uint8_t  _length;
-		uint8_t  _version;
-		uint8_t  _flags;
-		char _name;
-
-	public:
-
-		char   *name()   { return &_name; }
-		uint8_t length() { return _length - 5; }
-
-		static Rock_ridge* scan_name(uint8_t *ptr, uint8_t size)
-		{
-			Rock_ridge *rr = (Rock_ridge *)ptr;
-
-			while (rr->_length && ((uint8_t *)rr < ptr + size - 4)) {
-				if (rr->_signature == NM)
-					return rr;
-				rr = rr->next();
-			}
-
-			return 0;
-		}
-
-		Rock_ridge *next() { return (Rock_ridge *)((uint8_t *)this + _length); }
-};
-
-
-/*********************
- ** ISO9660 classes **
- *********************/
-
-/**
- * Access memory using offsets
- */
-class Iso::Iso_base
-{
-	protected:
-
-		template <typename T>
-		T value(int offset) { return *((T *)(this + offset)); }
-
-		template <typename T>
-		T ptr(int offset) { return (T)(this + offset); }
-};
-
-
-/**
- * Class representing a directory descriptions (see ECMA 119)
- */
-class Directory_record : public Iso::Iso_base
-{
-	enum {
-		TABLE_LENGTH = 33,  /* fixed length of directory record */
-
-		ROOT_DIR     = 0x0, /* special names of root and parent directories */
-		PARENT_DIR   = 0x1,
-
-		DIR_FLAG     = 0x2, /* directory flag in attributes */
-	};
-
-	public:
-
-		/* total length of this record */
-		uint8_t  record_length() { return value<uint8_t>(0); }
-
-		/* starting block of extent */
-		uint32_t blk_nr() { return value<uint32_t>(2); }
-
-		/* length in bytes of extent */
-		uint32_t data_length() { return value<uint32_t>(10); }
-
-		/* attributes */
-		uint8_t  file_flags() { return value<uint8_t>(25); }
-
-		/* length of file name */
-		uint8_t   file_name_length() { return value<uint8_t>(32); }
-
-		/* retrieve the file name */
-		void file_name(char *buf)
-		{
-			buf[0] = 0;
-
-			/* try Rock Ridge name */
-			Iso::Rock_ridge *rr = Iso::Rock_ridge::scan_name(system_use(),
-			                                            system_use_size());
-
-			if (rr) {
-				memcpy(buf, rr->name(), rr->length());
-				buf[rr->length()] = 0;
-				return;
-			}
-
-			/* retrieve iso name */
-			char *name = ptr<char*>(33);
-
-			/*
-			 * Check for root and parent directory names and modify them
-			 * to '.' and '..' respectively.
-			 */
-			if (file_name_length() == 1)
-
-				switch (name[0]) {
-
-				case PARENT_DIR:
-
-					buf[2] = 0;
-					buf[1] = '.';
-					buf[0] = '.';
-					return;
-
-				case ROOT_DIR:
-
-					buf[1] = 0;
-					buf[0] = '.';
-					return;
-				}
-
-			memcpy(buf, name, file_name_length());
-			buf[file_name_length()] = 0;
-		}
-
-		/* pad byte after file name (if file name length is even, only) */
-		uint8_t pad_byte() { return !(file_name_length() % 2) ? 1 : 0; }
-
-		/* system use area */
-		uint8_t *system_use()
-		{
-			return (uint8_t*)this + TABLE_LENGTH
-			        + file_name_length() + pad_byte();
-		}
-
-		/* length in bytes of system use area */
-		uint8_t system_use_size()
-		{
-			return record_length() - file_name_length()
-			       - TABLE_LENGTH - pad_byte();
-		}
-
-		/* retrieve next record */
-		Directory_record *next()
-		{
-			Directory_record *_next = this + record_length();
-
-			if (_next->record_length())
-				return _next;
-
-			return 0;
-		}
-
-		/* find a directory record with file name matching 'level' */
-		Directory_record *locate(char const *level)
-		{
-			Directory_record *dir = this;
-
-			while (dir) {
-
-				char name[Iso::LEVEL_LENGTH];
-				dir->file_name(name);
-
-				if (!strcmp(name, level))
-					return dir;
-
-				dir = dir->next();
-			}
-
-			return 0;
-		}
-
-		/* describes this record a directory */
-		bool directory() { return file_flags() & DIR_FLAG; }
-};
-
-
-/**
- * Volume descriptor (see ECMA 119)
- */
-class Volume_descriptor : public Iso::Iso_base
-{
-	enum {
-		/* volume types */
-		PRIMARY    = 0x01, /* type of primary volume descriptor */
-		TERMINATOR = 0xff, /* type of terminating descriptor */
-
-		/* constants */
-		ROOT_SIZE  = 34, /* the root directory record has a fixed length */
-	};
-
-	public:
-
-		/* descriptor type */
-		uint8_t type() { return value<uint8_t>(0); }
-
-		/* root directory record */
-		Directory_record * root_record() { return ptr<Directory_record *>(156); }
-
-		/* check for primary descriptor */
-		bool primary() { return type() == PRIMARY; }
-
-		/* check for terminating descriptor */
-		bool terminator() { return type() == TERMINATOR; }
-
-		/* copy the root record */
-		Directory_record *copy_root_record(Genode::Allocator &alloc)
-		{
-			return alloc.try_alloc(ROOT_SIZE).convert<Directory_record *>(
-
-				[&] (void *ptr) -> Directory_record * {
-					memcpy(ptr, root_record(), ROOT_SIZE);
-					return (Directory_record *)ptr; },
-
-				[&] (Allocator::Alloc_error e) -> Directory_record * {
-					Allocator::throw_alloc_error(e); }
-			);
-		}
-};
-
-
-/**
- * Locate the root-directory record in the primary volume descriptor
- */
-static Directory_record *locate_root(Genode::Allocator &alloc,
-                                     Block::Connection<> &block)
-{
-	/* volume descriptors in ISO9660 start at block 16 */
-	for (unsigned long blk_nr = 16;; blk_nr++) {
-		Iso::Sector sec(block, blk_nr, 1);
-		Volume_descriptor *vol = sec.addr<Volume_descriptor *>();
-
-		if (vol->primary())
-			return vol->copy_root_record(alloc);
-
-		if (vol->terminator())
-			return nullptr;
-	}
-}
-
-
-/**
- * Return root directory record
- */
-static Directory_record *root_dir(Genode::Allocator &alloc,
-                                  Block::Connection<> &block)
-{
-	Directory_record *root = locate_root(alloc, block);
-
-	if (!root) { throw Iso::Non_data_disc(); }
-
-	return root;
-}
-
-
-/*******************
- ** Iso interface **
- *******************/
-
-static Directory_record *_root_dir;
-
-
-Iso::File_info *Iso::file_info(Genode::Allocator &alloc,
-                               Block::Connection<> &block, char const *path)
-{
-	char level[PATH_LENGTH];
-
-	struct Scanner_policy_file
-	{
-		static bool identifier_char(char c, unsigned /* i */)
-		{
-			return c != '/' && c != 0;
-		}
-	};
-	typedef ::Genode::Token<Scanner_policy_file> Token;
-
-	Token t(path);
-
-	if (!_root_dir) {
-		_root_dir = root_dir(alloc, block);
-	}
-
-	Directory_record *dir = _root_dir;
-	uint32_t blk_nr = 0, data_length = 0;
-
-	/* determine block nr and file length on disk, parse directory records */
-	while (t) {
-
-		if (t.type() != Token::IDENT) {
-			t = t.next();
-			continue;
-		}
-
-		t.string(level, PATH_LENGTH);
-
-		/*
-		 * Save current block number in a variable because successive
-		 * iterations might override the memory location where dir points
-		 * to when a directory entry spans several sectors.
-		 */
-		uint32_t current_blk_nr = dir->blk_nr();
-
-		/* load extent of directory record and search for level */
-		for (unsigned long i = 0; i < Sector::to_blk(dir->data_length()); i++) {
-			Sector sec(block, current_blk_nr + i, 1);
-			Directory_record *tmp = sec.addr<Directory_record *>()->locate(level);
-
-			if (!tmp && i == Sector::to_blk(dir->data_length()) - 1) {
-				Genode::error("file not found: ", Genode::Cstring(path));
-				throw File_not_found();
-			}
-
-			if (!tmp) continue;
-
-			dir = tmp;
-			current_blk_nr = dir->blk_nr();
-
-			if (!dir->directory()) {
-				blk_nr      = current_blk_nr;
-				data_length = dir->data_length();
-			}
-
-			break;
-		}
-
-		t = t.next();
-	}
-
-	/* Warning: Don't access 'dir' after this point, since the sector is gone */
-
-	if (!blk_nr && !data_length) {
-		Genode::error("file not found: ", Genode::Cstring(path));
-		throw File_not_found();
-	}
-
-	return new (alloc) File_info(blk_nr, data_length);
-}
-
-
-unsigned long Iso::read_file(Block::Connection<> &block, File_info *info,
-                             off_t file_offset, uint32_t length, void *buf_ptr)
-{
-	uint8_t *buf = (uint8_t *)buf_ptr;
-	if (info->size() <= (size_t)(length + file_offset))
-		length = info->size() - file_offset - 1;
-
-	unsigned long total_blk_count = ((length + (Sector::blk_size() - 1)) &
-	                                 ~((Sector::blk_size()) - 1)) / Sector::blk_size();
-	unsigned long ret = total_blk_count;
-	unsigned long blk_count;
-	unsigned long blk_nr = info->blk_nr() + (file_offset / Sector::blk_size());
-
-	while ((blk_count = min<unsigned long>(Sector::MAX_SECTORS, total_blk_count))) {
-		Sector sec(block, blk_nr, blk_count);
-
-		total_blk_count -= blk_count;
-		blk_nr          += blk_count;
-
-		unsigned long copy_length = blk_count * Sector::blk_size();
-		memcpy(buf, sec.addr<void *>(), copy_length);
-
-		length -= copy_length;
-		buf    += copy_length;
-
-		/* zero out rest of page */
-		if (!total_blk_count && (blk_count % 2))
-			memset(buf, 0, Sector::blk_size());
-	}
-
-	return ret * Sector::blk_size();
-}
diff --git a/repos/os/src/server/iso9660/iso9660.h b/repos/os/src/server/iso9660/iso9660.h
deleted file mode 100644
index a937b69a44..0000000000
--- a/repos/os/src/server/iso9660/iso9660.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * \brief  ISO interface to session server
- * \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
- * \date   2010-07-26
- */
-
-/*
- * Copyright (C) 2010-2017 Genode Labs GmbH
- *
- * This file is part of the Genode OS framework, which is distributed
- * under the terms of the GNU Affero General Public License version 3.
- */
-
-/* Genode includes */
-#include <base/stdint.h>
-#include <block_session/connection.h>
-
-namespace Iso {
-
-	/*
-	 * Exceptions
-	 */
-	class Io_error : public Genode::Exception { };
-	class Non_data_disc : public Genode::Exception { };
-	class File_not_found : public Genode::Exception { };
-
-	/* enable/disable debugging output */
-	const int verbose = 0;
-
-	enum {
-		PATH_LENGTH  = 128, /* max. length of a path */
-		LEVEL_LENGTH = 32,  /* max. length of a level of a path */
-		PAGE_SIZE    = 4096,
-	};
-
-
-	class File_info
-	{
-		private:
-
-			Genode::uint32_t _blk_nr;
-			Genode::size_t   _size;
-
-		public:
-
-			File_info(Genode::uint32_t blk_nr, Genode::size_t size)
-			: _blk_nr(blk_nr), _size(size) {}
-
-			Genode::uint32_t blk_nr() { return _blk_nr; }
-			Genode::size_t   size()   { return _size;   }
-			Genode::size_t   page_sized() { return (_size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); }
-	};
-
-
-	/*******************
-	 ** Iso interface **
-	 *******************/
-
-	/**
-	 * Retrieve file information
-	 *
-	 * \param alloc allocator used for File_info object
-	 * \param block Block session used to read sectors from ISO
-	 * \param path  absolute path of the file (slash separated)
-	 *
-	 * \throw File_not_found
-	 * \throw Io_error
-	 * \throw Non_data_disc
-	 *
-	 * \return Pointer to File_info class
-	 */
-	File_info *file_info(Genode::Allocator &alloc, Block::Connection<> &block, char const *path);
-
-	/**
-	 * Read data from ISO
-	 *
-	 * \param block Block session used to read sectors from ISO
-	 * \param info File    Info of file to read the data from
-	 * \param file_offset  Offset in file
-	 * \param length       Number of bytes to read
-	 * \param buf          Output buffer
-	 *
-	 * \throw Io_error
-	 *
-	 * \return Number of bytes read
-	 */
-	unsigned long read_file(Block::Connection<> &block, File_info *info, Genode::off_t file_offset,
-	                        Genode::uint32_t length, void *buf);
-} /* namespace Iso */
diff --git a/repos/os/src/server/iso9660/main.cc b/repos/os/src/server/iso9660/main.cc
deleted file mode 100644
index fa41152716..0000000000
--- a/repos/os/src/server/iso9660/main.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * \brief  Rom-session server for ISO file systems
- * \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
- * \date   2010-07-26
- */
-
-/*
- * Copyright (C) 2010-2017 Genode Labs GmbH
- *
- * This file is part of the Genode OS framework, which is distributed
- * under the terms of the GNU Affero General Public License version 3.
- */
-
-/* Genode includes */
-#include <base/component.h>
-#include <base/heap.h>
-#include <base/log.h>
-#include <base/rpc_server.h>
-#include <dataspace/client.h>
-#include <root/component.h>
-#include <util/avl_string.h>
-#include <base/attached_ram_dataspace.h>
-#include <base/session_label.h>
-#include <block_session/connection.h>
-#include <rom_session/rom_session.h>
-
-/* local includes */
-#include "iso9660.h"
-
-using namespace Genode;
-
-
-/*****************
- ** ROM service **
- *****************/
-
-namespace Iso {
-
-	typedef Avl_string<PATH_LENGTH> File_base;
-	typedef Avl_tree<Avl_string_base> File_cache;
-
-	class File;
-	class Rom_component;
-
-	typedef Genode::Root_component<Rom_component> Root_component;
-
-
-	class Root;
-}
-
-
-/**
- * File abstraction
- */
-class Iso::File : public File_base
-{
-	private:
-
-		/*
-		 * Noncopyable
-		 */
-		File(File const &);
-		File &operator = (File const &);
-
-		Genode::Allocator        &_alloc;
-
-		File_info                *_info;
-		Attached_ram_dataspace    _ds;
-
-	public:
-
-		File(Genode::Env &env, Genode::Allocator &alloc,
-		     Block::Connection<> &block, char const *path)
-		:
-			File_base(path), _alloc(alloc),
-			_info(Iso::file_info(_alloc, block, path)),
-			_ds(env.ram(), env.rm(), align_addr(_info->page_sized(), 12))
-		{
-			Iso::read_file(block, _info, 0, _ds.size(), _ds.local_addr<void>());
-		}
-		
-		~File() { destroy(_alloc, _info); }
-
-		Dataspace_capability dataspace() { return _ds.cap(); }
-};
-
-
-class Iso::Rom_component : public Genode::Rpc_object<Rom_session>
-{
-	private:
-
-		/*
-		 * Noncopyable
-		 */
-		Rom_component(Rom_component const &);
-		Rom_component &operator = (Rom_component const &);
-
-		File *_file = nullptr;
-
-		File *_lookup(File_cache &cache, char const *path)
-		{
-			return static_cast<File *>(cache.first() ?
-			                           cache.first()->find_by_name(path) :
-			                           0);
-		}
-
-	public:
-
-		Rom_dataspace_capability dataspace() override {
-			return static_cap_cast<Rom_dataspace>(_file->dataspace()); }
-
-		void sigh(Signal_context_capability) override { }
-
-		Rom_component(Genode::Env &env, Genode::Allocator &alloc,
-		              File_cache &cache, Block::Connection<> &block,
-		              char const *path)
-		{
-			if ((_file = _lookup(cache, path))) {
-				Genode::log("cache hit for file ", Genode::Cstring(path));
-				return;
-			}
-
-			_file = new (alloc) File(env, alloc, block, path);
-			Genode::log("request for file ", Genode::Cstring(path));
-
-			cache.insert(_file);
-		}
-};
-
-
-class Iso::Root : public Iso::Root_component
-{
-	private:
-
-		Genode::Env       &_env;
-		Genode::Allocator &_alloc;
-
-		Allocator_avl       _block_alloc { &_alloc };
-		Block::Connection<> _block       { _env, &_block_alloc };
-
-		/*
-		 * Entries in the cache are never freed, even if the ROM session
-		 * gets destroyed.
-		 */
-		File_cache _cache { };
-
-		char _path[PATH_LENGTH];
-
-	protected:
-
-		Rom_component *_create_session(const char *args) override
-		{
-			size_t ram_quota =
-				Arg_string::find_arg(args, "ram_quota").ulong_value(0);
-			size_t session_size =  sizeof(Rom_component) + sizeof(File_info);
-			if (ram_quota < session_size)
-				throw Insufficient_ram_quota();
-
-			Session_label const label = label_from_args(args);
-			copy_cstring(_path, label.last_element().string(), sizeof(_path));
-
-			if (verbose)
-				Genode::log("Request for file ", Cstring(_path), " len ", strlen(_path));
-
-			try {
-				return new (_alloc) Rom_component(_env, _alloc, _cache, _block, _path);
-			}
-			catch (Io_error)       { throw Service_denied(); }
-			catch (Non_data_disc)  { throw Service_denied(); }
-			catch (File_not_found) { throw Service_denied(); }
-		}
-
-	public:
-
-		Root(Genode::Env &env, Allocator &alloc)
-		:
-			Root_component(&env.ep().rpc_ep(), &alloc),
-			_env(env), _alloc(alloc)
-		{ }
-};
-
-
-struct Main
-{
-	Genode::Env  &_env;
-	Genode::Heap  _heap { _env.ram(), _env.rm() };
-
-	Iso::Root     _root { _env, _heap };
-
-	Main(Genode::Env &env) : _env(env)
-	{
-		_env.parent().announce(_env.ep().manage(_root));
-	}
-};
-
-
-void Component::construct(Genode::Env &env) { static Main main(env); }
diff --git a/repos/os/src/server/iso9660/target.mk b/repos/os/src/server/iso9660/target.mk
deleted file mode 100644
index 3d6d3a7950..0000000000
--- a/repos/os/src/server/iso9660/target.mk
+++ /dev/null
@@ -1,5 +0,0 @@
-TARGET = iso9660
-SRC_CC = main.cc iso9660.cc
-LIBS   = base
-
-CC_CXX_WARN_STRICT_CONVERSION =