Сторонние исходники и установка
Использование сторонних исходников:
- Old school. Скопировать и поддерживать самим
- + если апстрим умер/заснул, а ты не готов сам становиться апстримом, но твою задачу код решает
- + если требуется модификация, которую апстрим не примет
- - поддерживать самим
- New school: взаимодействовать с апстримом
- Linux way: если это библиотека — зачем исходники, она ж в пакете есть!
- + собирать один раз
- - выпиливать из апстрима
- (а пакет кто делает?)
Patch / diff
Рассмотрим ситуацию, когда сторонние исходники используются, но нужно их немного поправить:
- Потому что это правка, необходимая только вашему проекту
- Потому что вы — майнтейнер пакета в Linux, а исходники не соответствуют принятой в вашем сообществе дисциплине, или апстрим не считает багу ошибкой, а вы считаете ☺
- …
В любом случае при обновлении апстрима исправления придётся вносить заново
Цикл обновления / внесения изменений:
- Первоначальный импорт исходников
- Адаптация к вашим нуждам
Оформление серии патчей (см. ниже)
- Релиз
- Отныне и навеки
- Обновление апстримной версии
- Применение патчей к обновлённой версии
- Адаптация отвалившихся патчей
- Релиз
Утилита diff — построчное сравнение и демонстрация изменений двух текстов
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <ctype.h>
6
7 /* open()->fopen() wrapper */
8 static FILE *ffopen(const char *pathname, const char *mode, int flags)
9 {
10 int m;
11
12 switch(tolower(mode[0])+(mode[1]=='+')) {
13 case 'b': /* "a+" */
14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
15 case 'r': m = O_RDONLY; break;
16 case 's': m = O_RDWR; break; /* "r+" */
17 case 'x': /* "w+" */
18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
19 default: m = O_RDONLY; break;
20 }
21
22 int fd = open(pathname, flags, m);
23 return fd<0? NULL: fdopen(fd, mode);
24 }
25
26 int main(int argc, char *argv[]) {
27 FILE *fp;
28
29 fp = ffopen(argv[1], "r", O_NOFOLLOW);
30 if(fp == NULL) {
31 perror(argv[1]);
32 return 1;
33 }
34
35 return 0;
36 }
|
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <ctype.h>
6
7 /* open()->fopen() wrapper */
8 static FILE *ffopen(const char *pathname, const char *mode, int flags)
9 {
10 int m;
11
12 /* Simulate fopen flags */
13 switch(tolower(mode[0])+(mode[1]=='+')) {
14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
15 case 'b': /* "a+" */
16 case 'r': m = O_RDONLY; break;
17 case 's': m = O_RDWR; break; /* "r+" */
18 case 'x': /* "w+" */
19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
20 default: m = O_RDONLY; break;
21 }
22
23 int fd = open(pathname, flags, m);
24 return fd<0? NULL: fdopen(fd, mode);
25 }
26
27 int main(int argc, char *argv[]) {
28 FILE *fp;
29
30 fp = ffopen(argv[1], "r", O_NOFOLLOW);
31 if(fp == NULL) {
32 perror(argv[1]);
33 return 1;
34 }
35
36 return 0;
37 }
|
Просто diff: как из одного файла сделать другой
diff -e — команды для текстового редактора ed
diff -c — разница вместе с окружающим её контекстом
1 *** ffopen.c 2020-06-13 20:58:46.702703936 +0300 2 --- ffopen_new.c 2020-12-08 19:53:02.082341887 +0300 3 *************** 4 *** 9,17 **** 5 { 6 int m; 7 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 case 'r': m = O_RDONLY; break; 12 case 's': m = O_RDWR; break; /* "r+" */ 13 case 'x': /* "w+" */ 14 --- 9,18 ---- 15 { 16 int m; 17 18 + /* Simulate fopen flags */ 19 switch(tolower(mode[0])+(mode[1]=='+')) { 20 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 21 + case 'b': /* "a+" */ 22 case 'r': m = O_RDONLY; break; 23 case 's': m = O_RDWR; break; /* "r+" */ 24 case 'x': /* "w+" */
diff -u — наиболее удобный формат
1 --- ffopen.c 2020-06-13 20:58:46.702703936 +0300 2 +++ ffopen_new.c 2020-12-08 19:53:02.082341887 +0300 3 @@ -9,9 +9,10 @@ 4 { 5 int m; 6 7 + /* Simulate fopen flags */ 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 + case 'b': /* "a+" */ 12 case 'r': m = O_RDONLY; break; 13 case 's': m = O_RDWR; break; /* "r+" */ 14 case 'x': /* "w+" */
@@ -9,9 +9,10 @@ означает: «в строчке 9 исходного файла взять 9 строк и превратить их в 10 строк целевого файла»
Есть варианты -C размер контекста и -U размер контекста
- Вариант с несколькими изменениями (chunks):
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 int main(int argc, char *argv[]) { 27 FILE *fp; 28 29 fp = ffopen(argv[1], "r", O_NOFOLLOW); 30 if(fp == NULL) { 31 perror(argv[1]); 32 return 1; 33 } 34 35 return 0; 36 }
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 /* Simulate fopen flags */ 13 switch(tolower(mode[0])+(mode[1]=='+')) { 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'b': /* "a+" */ 16 case 'r': m = O_RDONLY; break; 17 case 's': m = O_RDWR; break; /* "r+" */ 18 case 'x': /* "w+" */ 19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 20 default: m = O_RDONLY; break; 21 } 22 23 int fd = open(pathname, flags, m); 24 return fd<0? NULL: fdopen(fd, mode); 25 } 26 27 int main(int argc, char *argv[]) { 28 FILE *fp; 29 30 fp = ffopen(argv[1], "r", O_NOFOLLOW); 31 if(fp == NULL) { 32 perror(argv[1]); 33 return 1; 34 } 35 else 36 fclose(fp); 37 38 return 0; 39 }
- ⇒
1 --- ffopen.c 2020-12-08 20:29:07.452395896 +0300 2 +++ ffopen_new.c 2020-12-08 20:12:38.004914230 +0300 3 @@ -9,9 +9,10 @@ 4 { 5 int m; 6 7 + /* Simulate fopen flags */ 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 + case 'b': /* "a+" */ 12 case 'r': m = O_RDONLY; break; 13 case 's': m = O_RDWR; break; /* "r+" */ 14 case 'x': /* "w+" */ 15 @@ -31,6 +32,8 @@ 16 perror(argv[1]); 17 return 1; 18 } 19 + else 20 + fclose(fp); 21 22 return 0; 23 }
Утилита patch умеет применять результат diff к исходному файлу, при этом получается целевой файл
Этот-то «результат diff» и называется патчем
- А несколько таких патчей в правильном порядке — патчсетом
Что гораздо важнее, patch умеет находить перемещение контекста и даже определять приблизительное совпадение контекста:
Возьмём слегка изменённый файл ffopen2.c (в нём изменена строка case 'x': и добавлен комментарий перед main()):
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* alias for "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 /* just ordinary main() */ 27 int main(int argc, char *argv[]) { 28 FILE *fp; 29 30 fp = ffopen(argv[1], "r", O_NOFOLLOW); 31 if(fp == NULL) { 32 perror(argv[1]); 33 return 1; 34 } 35 36 return 0; 37 }
и применим к нему патч, предназначенный для исходного файла:
Контекст первого блока неточен (в патче одна строка контекста слегка другая); тем не менее это очень похожий контекст (с разницей 1, что бы это ни значило), так что блок применён
- Контекст второго блока точен, но найден со смещением в одну строку, блок применён
Получившийся файл имеет свойства как общего предка (ffopen.c), так и обоих родителей (ffopen2.c и ffopen_changed.c)
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 int main(int argc, char *argv[]) { 27 FILE *fp; 28 29 fp = ffopen(argv[1], "r", O_NOFOLLOW); 30 if(fp == NULL) { 31 perror(argv[1]); 32 return 1; 33 } 34 35 return 0; 36 }
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 /* Simulate fopen flags */ 13 switch(tolower(mode[0])+(mode[1]=='+')) { 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'b': /* "a+" */ 16 case 'r': m = O_RDONLY; break; 17 case 's': m = O_RDWR; break; /* "r+" */ 18 case 'x': /* alias for "w+" */ 19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 20 default: m = O_RDONLY; break; 21 } 22 23 int fd = open(pathname, flags, m); 24 return fd<0? NULL: fdopen(fd, mode); 25 } 26 27 /* just ordinary main() */ 28 int main(int argc, char *argv[]) { 29 FILE *fp; 30 31 fp = ffopen(argv[1], "r", O_NOFOLLOW); 32 if(fp == NULL) { 33 perror(argv[1]); 34 return 1; 35 } 36 else 37 fclose(fp); 38 39 return 0; 40 }
В случае, когда какие-то блоки применяются, а какие-то — нет, patch создаёт reject-файл — патч, содержащий только неприложившиеся блоки.
Например, модифицируем в ffopen.c строчку case 'b'::
- Поскольку изменения сделаны прямо в исправляемой строке, такой блок не приложится; второй блок приложится нормально:
1 $ patch --verbose ffopen3_changed.c < changeset.patch 2 Hmm... Looks like a unified diff to me... 3 The text leading up to this was: 4 -------------------------- 5 |--- ffopen.c 2020-12-08 20:29:07.452395896 +0300 6 |+++ ffopen_new.c 2020-12-08 20:12:38.004914230 +0300 7 -------------------------- 8 patching file ffopen3_changed.c 9 Using Plan A... 10 Hunk #1 FAILED at 9. 11 Hunk #2 succeeded at 31. 12 1 out of 2 hunks FAILED -- saving rejects to file ffopen3_changed.c.rej 13 done 14
- Этот reject файл предполагается открыть редактором и ручками применить
Использование git
Команда git diff порождает не что иное, как diff -u. Предположим, мы отредактировали файл ffopen.c в git-репозитории. Вот что покажет git diff:
1 diff --git a/ffopen.c b/ffopen.c 2 index 7ac8822..bf6ba89 100644 3 --- a/ffopen.c 4 +++ b/ffopen.c 5 @@ -9,9 +9,10 @@ static FILE *ffopen(const char *pathname, const char *mode, int flags) 6 { 7 int m; 8 9 + /* Simulate fopen flags */ 10 switch(tolower(mode[0])+(mode[1]=='+')) { 11 - case 'b': /* "a+" */ 12 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 13 + case 'b': /* "a+" */ 14 case 'r': m = O_RDONLY; break; 15 case 's': m = O_RDWR; break; /* "r+" */ 16 case 'x': /* "w+" */ 17 @@ -31,6 +32,8 @@ int main(int argc, char *argv[]) { 18 perror(argv[1]); 19 return 1; 20 } 21 + else 22 + fclose(fp); 23 24 return 0; 25 }
Поскольку git сравнивает две версии одного и того же файла, для большей понятности исходный файл считается лежащим в воображаемом каталоге a/, а целевой — в воображаемом каталоге b/.
В заголовке патча присутствуют (урезанные) идентификаторы сравниваемых объектов (7ac8822 и bf6ba89)
Умный git догадался, что это файл на Си (?) и в описании контекста добавил сигнатуру функции, в которой он встретился (это комментарий)
Если сейчас сделать commit, тот же формат будет у git log -p (+загловок коммита)
- Из последовательности коммитов можно сделать патчсет (в примере патч один)
1 $ git format-patch HEAD^ 2 0001-FFopen-updated.patch 3 $ head -20 0001-FFopen-updated.patch 4 From e3522f55a58d7f3ea6d85d518ef55a9d36ce887e Mon Sep 17 00:00:00 2001 5 From: "George V. Kouryachy (Fr. Br. George)" <george@altlinux.ru> 6 Date: Tue, 8 Dec 2020 21:11:51 +0300 7 Subject: [PATCH] FFopen updated 8 9 --- 10 ffopen.c | 5 ++++- 11 1 file changed, 4 insertions(+), 1 deletion(-) 12 13 diff --git a/ffopen.c b/ffopen.c 14 index 7ac8822..bf6ba89 100644 15 --- a/ffopen.c 16 +++ b/ffopen.c 17 @@ -9,9 +9,10 @@ static FILE *ffopen(const char *pathname, const char *mode, int flags) 18 { 19 int m; 20 21 + /* Simulate fopen flags */ 22 switch(tolower(mode[0])+(mode[1]=='+')) { 23 - case 'b': /* "a+" */ 24
Это патч можно применить к старой версии файла!
С помощью patch -p1 < 0001-FFopen-updated.patch
Если не указывать, какой файл патчим, patch возьмёт её из заголовка
Поэтому надо с помощью -p удалить один уровень воображаемых каталогов a/ или b/
С помощью git apply 0001-FFopen-updated.patch
С помощью git am 0001-FFopen-updated.patch (при этом произведётся также и commit с данными из заголовка)
Важно: правила наложения патчей в git очень строгие: fuzz не допускается. Возможно, накладывать старый патч на обновлённые исходники лучше всё-таки patch-ем
Сочетание git и patch
Если никакого git-репозитория нет, всё равно удобно использовать git:
- Создаём git-репозиторий
- Накатываем патч
- Часть блоков прикладывается, част отваливается
- Коммитим то, что приложилось
- Глядя в reject-файлы применяем вручную то, что не приложилось, проверяем сборку, и тоже коммитим, возможно, несколько раз
Можно таким образом превратить исходный патч в патчсет, а можно слить всё обратно в один коммит с помощью git rebase -i состояние_до_накладывания_патча и последующего fixup
Установка
Есть три способа установить ПО под Linux:
- Не устанавливать. Собрать всё в каком-то подкаталоге и запускать оттуда.
- Много лишних файлов
Не очень понятно как читать man
Скорее всего, не будет работать, если мы не сделаем cd этот каталог
- Установить в специальный отдельный подкаталог системы все нужные файлы
Как правило, это /opt/название-приложения
Требует прав root
Различаются два вида файлов — те, что установлены в /opt/название-приложения и те, которые образуются в процессе работы приложения (например, локальные конфиги в $HOME/.config/название-приложения. Это надо предусмотреть в коде программы.
Кто за вас бкдет подгружать разделяемые библиотеки из /opt/название-приложения/lib? Никто. Надо разбираться с $LD_LIBRARY_PATH
Само приложение придётся запускать /opt/название-приложения/bin/название-приложения (или как-то так), либо для каждого добавлять /opt/название-очередного-приложения/bin/ в $PATH
Удаление такого приложения — просто удаление каталога /opt/название-приложения
( так делать не надо ) Установить руками в соответствующие каталоги (/usr/bin/ для бинарников, /usr/lib64 для библиотек и т. д.)
- А если там файлы из пакетов лежали?
- А как потом удалять?
- Админ увидит — руки оторвёт…
Вариант предыдущего — /usr/local/… вместо /usr — ненамного лучше
- В случае linux там мало что лежит
- Всё остальное так же плохо
Сформировать пакет
- Установка, удаление, интеграция с системой, …
- Надо уметь собирать пакеты ☹
Если вы не майнтейнер пакета в определённом сообществе, а апстрим, придётся собирать много разных пакетов, что ли??
( за это вам уготованы мучения в аду ) Использовать самописный «инсталлятор»
Вывод:
- Несвободный софт; громадный проект, который никто, кроме вас, собирать не будет
/opt/что-там
пакет под ваш любимый, хорошо известный вам дистрибутив (или систему дистрибуции, вроде AppImage, Flatpak и т. п.)
- Свободный софт:
- Процедура сборки под любое вменяемое окружение, пускай майнтейнеры сами разбираются
Поддержка установки в automake
В сформированном скрипте configure присутствует много настроек именно каталогов установки:
$ ./configure --help `configure' configures L10n gettext example 0.0 to adapt to many kinds of systems. … Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [/usr/local] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, `make install' will install all the files in `/usr/local/bin', `/usr/local/lib' etc. You can specify an installation prefix other than `/usr/local' using `--prefix', for instance `--prefix=$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] …
--prefix указывает, в каком каталоге будет создана uniх-подобная иерархия подкаталогов, и где запущенная программа должна искать свои файлы
По умолчанию это /usr/local, т. е. самое бессмысленное место в Linux ☺
Установка
В gettext поддерживается цель install и монжество её подцелей
- Дерево нужных каталогов формируется автоматически
Для установки напрямую в систему и удаления оттуда с правами root поддерживаются цели install и uninstall
см. выше замечания, декорированные подобными иконками ☺
- (никогда так не делайте, да)
Проект, собранный для работы в определённом prefix, можно установить в произвольный каталог с помощью make install DESTDIR=произвольный_каталог
При этом в этом каталоге будет создан prefix и развёрнуто дерево соответствующих подкаталогов
Сама программа, если её запустить из произвольный_каталог/usr/bin скорее всего, правильно не заработает (не найдёт переводов, например)
Это нужно для безопасного формирования двоичной дистрибуции вашей програмы (например, из этого можно сделать пакет)
Как узнать параметры установки внутри самой прогарммы?
Autoconf умеет генерировать название-приложения из его полного описания, но это немножко искусственный мозг, поэтому имеет смысл использовать четвёртый параметр AC_INIT — т. н. «tarname»:
AC_INIT([L10n gettext example], [0.0], [george@altlinux.org], [ahello])
Если поискать этот «tarname» в генератах, окажется что он присутствует config.h в виде аж двух макросов — PACKAGE и PACKAGE_TARNAME
- ⇒ хотите знать это название, просто инклюдьте этот файл
Также оно присутствует в configure и Makefilea под именем PACKAGE
А вот каталог, куда предполагается устанавливать переводы, присутствует только в configure и Makefile под именем localedir
⇒ хотите знать этот путь, добавьте в Makefile.am что-то вроде AM_CPPFLAGS = -D LOCALEDIR='"$(localedir)"'
Замечание 1: для gettext есть специальный m4-модуль: Tutorial на эту тему
В нём поддерживаются цели установки переводов и перегенерации переводов, а также добавляются нужные макросы в config.h
См. пример
Д/З
Взять решение Д/З с локализацией и обеспечить в нём
Примитивный man (любым способом)
Сборку и установку в произвольный каталог (для autotools — с помощью --prefix), содержащий стандартные подкаталоги
- Убедиться, что установленное таком образом приложение находит файлы с переводами в подкаталогах этого каталога
Сборку по стандартной схеме (для autotools это делается с помощью --prefix=/usr) и установку в произвольный подкаталог (для autotools это делается с помощью make DESTDIR=произвольный_каталог)
- Убедиться, что
бинарники установлены в произвольный_каталог/usr/bin/,
русские переводы установлены в произвольный_каталог/usr/share/locale/ru/LC_MESSAGES/
manpage установлена в произвольный_каталог/usr/share/man/man1/
Убеждаться в этом не обязательно, но собранная таким образом программа должна искать перевод в /usr/share/locale/ru/LC_MESSAGES/…
- Убедиться, что
Полученный код, очищенный от генератов, поместить в подкаталог 12_PatchInstall отчётного git-репозитория