Linux shared objects directory

How to Create a Shared Directory for All Users in Linux

As a system administrator, you may have a certain directory that you want to give read/write access to every user on a Linux server. In this guide, we will review how to enable write access to all users on a particular directory (shared directory) in Linux.

This calls for setting the appropriate access permissions, and the most effective as well as reliable method to allocating a common group for all the users who will share or have write access to the specific directory.

So, start by creating the directory and common group in case it doesn’t already exist on the system as follows:

Then add an existing user who will have write access to the directory: /var/www/reports/ to the group project as below.

Create Common Directory Group

The flags and arguments used in the above command are:

  1. -a – which adds the user to the supplementary group.
  2. -G – specifies the group name.
  3. project – group name.
  4. tecmint – existing username.

Afterwards, proceed to configure the appropriate permissions on the directory, where the option -R enables recursive operations into subdirectories:

Explaining the permissions 2775 in the chmod command above:

  1. 2 – turns on the setGID bit, implying–newly created subfiles inherit the same group as the directory, and newly created subdirectories inherit the set GID bit of the parent directory.
  2. 7 – gives rwx permissions for owner.
  3. 7 – gives rwx permissions for group.
  4. 5 – gives rx permissions for others.

You can create more system users and add them to the directory group as follows:

Then create subdirectories where the new users above will store their project reports:

Now you can create files/folders and share with other users on the same group.

That’s it! In this tutorial, we reviewed how to enable write access to all users on a particular directory. To understand more about users/groups in Linux, read How to Manage Users/Groups File Permissions and Attributes.

Remember to offer us your thoughts about this article via the feedback form below.

If You Appreciate What We Do Here On TecMint, You Should Consider:

TecMint is the fastest growing and most trusted community site for any kind of Linux Articles, Guides and Books on the web. Millions of people visit TecMint! to search or browse the thousands of published articles available FREELY to all.

If you like what you are reading, please consider buying us a coffee ( or 2 ) as a token of appreciation.

We are thankful for your never ending support.

Источник

Intro to Linux Shared Libraries (How to Create Shared Libraries)

A library is a file containing compiled code from various object files stuffed into a single file. It may contain a group of functions that are used in a particular context. For example, the ‘pthread’ library is used when thread related functions are to be used in the program.

Broadly, a library (or Program Library) can be of two types :

  1. Shared Library
  2. Static Library

In this article we will discuss specifically about Shared Libraries.

Shared Libraries

Shared Libraries are the libraries that can be linked to any program at run-time. They provide a means to use code that can be loaded anywhere in the memory. Once loaded, the shared library code can be used by any number of programs. So, this way the size of programs(using shared library) and the memory footprint can be kept low as a lot of code is kept common in form of a shared library.

Shared libraries provide modularity to the development environment as the library code can be changed, modified and recompiled without having to re-compile the applications that use this library. For example, for any change in the pthread library code, no change is required in the programs using pthread shared library. A shared library can be accessed through different names :

  • Name used by linker (‘lib’ followed by the library name, followed by ‘.so’ . For example libpthread.so)
  • Fully qualified name or soname ( ‘lib’ followed by the library name, followed by ‘.so’, followed by ‘.’ and a version number. For example : libpthread.so.1)
  • Real name (‘lib’ followed by the library name, followed by ‘.so’, followed by ‘.’ and a version number, followed by a ‘.’ and a minor number, followed by a ‘.’ and a release number. Release number is optional. For example, libpthread.so.1.1)

A version number is changed for a shared library when the changes done in the code make the shared library incompatible with the previous version. For example, if a function is completely removed then a new version of the library is required.

A minor number is changed in case there is a modification in the code that does not make the shared library incompatible with the previous version being used. For example, a small bug fix won’t break the compatibility of the existing shared library so only a minor number is changed while version remains the same.

Now, one may wonder why so many names for a shared library?

Well, these naming conventions help multiple versions of same shared library to co-exist in a system. The programs linking with the shared library do not need to take care about the latest version of the shared library installed in the system. Once the latest version of the shared library is installed successfully, all the programs automatically start linking to the latest version.

The name used by linker is usually a symbolic link to the fully qualified soname which in turn is a symbolic link to the real name.

Placement in File System

There are mainly three standard locations in the filesystem where a library can be placed.

We will go by the Filesystem Hierarchy standards(FHS) here. According to the FHS standards, All the libraries which are loaded at start up and running in the root filesystem are kept in /lib. While the libraries that are used by system internally are stored at /usr/lib. These libraries are not meant to be directly used by users or shell scripts. There is a third location /usr/local/lib( though it is not defined in the latest version of FHS ). If it exists, it contains all the libraries that are not part of standard distribution. These non-standard libraries are the one’s which you download and could be possibly buggy.

Using ldconfig

Once a shared library is created, copy the shared library to directory in which you want the library to reside (for example /usr/local/lib or /usr/lib). Now, run ldconfig command in this directory.

What does ldconfig do?

You remember that we discussed earlier that a linker name for shared library is a symbolic link to the fully qualified soname which in turn is a symbolic link to the real name. Well, this command does exactly the same.

When you run an ELF executable, by default the loader is run first. The loader itself is a shared object file /lib/ld-linux.so.X where ‘X’ is a version number. This loader in turn finds and loads all the shared libraries on which our program depends.

Читайте также:  Что такое диспетчер realtek hd для windows

All the directories that are searched by the loader in order to find the libraries is stored in /etc/ld.so.conf. Searching all the directories specified in /etc/ld.so.conf file can be time consuming so every time ldconfig command is run, it sets up the required symbolic links and then creates a cache in file /etc/ld.so.cache where all the information required for executable is written. Reading information from cache is very less time consuming. The catch here is that ldconfig command needs to be run every-time a shared library is added or removed. So on start-up the program uses /etc/ld.so.cache to load the libraries it requires.

Using Non Standard Library Locations

When using non standard library locations. One of the following three steps could be carried out :

Add the path to /etc/ld.so.conf file. This file contains paths to all the directories in which the library is searched by the loader. This file could sometime contain a single line like :

In that case, just create a conf file in the same directory. You can directly add a directory to cache by using the following command :

Note that this is a temporary change and will be lost once the system is rebooted. Update the environment variable LD_LIBRARY_PATH to point to your directory containing the shared library. Loader will use the paths mentioned in this environment variable to resolve dependencies.

Note that on some Unix systems the name of the environment variable could differ.

Note: On a related topic, as we explained earlier, there are four main stages through which a source code passes in order to finally become an executable.

Example (How to Create a Shared Library)

Lets take a simple practical example to see how we can create and use shared libraries. The following is the piece of code (shared.c) that we want to put in a shared library :

shared.h looks like :

Lets first make shared.c as a shared library.

1. Run the following two commands to create a shared library :

The first command compiles the code shared.c into position independent code which is required for a shared library.
The second command actually creates a shared library with name ‘libshared.so’.

2. Here is the code of the program that uses the shared library function ‘add()’

3. Next, run the following command :

This command compiles the main.c code and tells gcc to link the code with shared library libshared.so (by using flag -l) and also tells the location of shared file(by using flag -L).

4. Now, export the path where the newly created shared library is kept by using the following command :

The above command exports the path to the environment variable ‘LD_LIBRARY_PATH’.

5. Now run the executable ‘main’ :

So we see that shared library was loaded and the add function inside it was executed.

Источник

C: создание и применение shared library в Linux

Библиотека — это файл, содержащий скопилированный код из нескольких объектных файлов в один файл библиотеки, который может содержать функции используемые другими программами.

Библиотеки могут быть статичными (static) и динамическими или разделяемыми (dynamic, shared).

Ниже — краткий пример создания и применения shared library на C в Linux.

Доступ к общей библиотеке может осуществляться по нескольким именам:

  • имя, используемое «линкером» /usr/bin/ld (linker name), в виде слова lib + имя библиотеки + расширение .so , например — libpthread.so
  • Полное имя (fully qualified name или soname), в виде lib + name + .so + версия (например — libpthread.so.1 )
  • реальное имя — полное имя файла, содержащего версию библиотеки, в виде lib + имя + .so + версия + минорная версия и опционально — версия релиза (например — libpthread.so.1.1 )

Версия для общей бибилиотеки меняется в случае, когда изменения в коде этой бибилиотеки делают её несовместимой с предыдущими версиями, например — если из библиотеки была убрана какая-то функция ( libpthread.so.1 )

Минорная версия меняется, если изменения не затронули совметимость библиотеки, например — какой-то фикс в одной из функций. В таком случае версия останется прежней, а изменится только минорная часть ( libpthread.so.1.1 ).

Такое соглашение об именах версий библиотек позволяет существование разных версий одной библиотеки в одной системе.

Программа, которая будет линковаться с этой бибилиотекой, не будет привязана к определённому файлу с последней версией библиотеки. Вместо этого, после установки последней версии — все связанные программы будут использовать её.

Создание библиотеки

Создадим простой файл libhello.c с одной функцией:

Создаём заголовочный файл библиотеки libhello.h с прототипом функции:

Приступаем к сборке библиотеки.

Создаём объектный файл, указав опцию PIC (Position Independent Code), Warning ( -Wall — warning all), -g для добавления дебаг-информации и -c — что бы создать только файл библиотеки, без вызова линкера:

Проверяем — теперь у нас имеется объектный файл .o :

Источник

Static, Shared Dynamic and Loadable Linux Libraries

This tutorial discusses the philosophy behind libraries and the creation and use of C/C++ library «shared components» and «plug-ins». The various technologies and methodologies used and insight to their appropriate application, is also discussed. In this tutorial, all libraries are created using the GNU Linux compiler.

Related YoLinux Tutorials:

Libraries employ a software design also known as «shared components» or «archive libraries», which groups together multiple compiled object code files into a single file known as a library. Typically C functions/C++ classes and methods which can be shared by more than one application are broken out of the application’s source code, compiled and bundled into a library. The C standard libraries and C++ STL are examples of shared components which can be linked with your code. The benefit is that each and every object file need not be stated when linking because the developer can reference the library collective. This simplifies the multiple use and sharing of software components between applications. It also allows application vendors a way to simply release an API to interface with an application. Components which are large can be created for dynamic use, thus the library can remain separate from the executable reducing it’s size and thus less disk space is used for the application. The library components are then called by various applications for use when needed.

Benefits include:

  • Component reuse: update one library, shared resource takes up less disk space.
  • Version management: Linux libraries can cohabitate old and new versions on a single system.
  • Component Specialization: niche and specialized developers can focus on their core competency on a single library. Simplifies testing and verification.

There are two Linux C/C++ library types which can be created:

  1. Static libraries (.a): Library of object code which is linked with, and becomes part of the application.
  2. Dynamically linked shared object libraries (.so): There is only one form of this library but it can be used in two ways.
    1. Dynamically linked at run time. The libraries must be available during compile/link phase. The shared objects are not included into the executable component but are tied to the execution.
    2. Dynamically loaded/unloaded and linked during execution (i.e. browser plug-in) using the dynamic linking loader system functions.

Library naming conventions:

Consider the following compile and link command: gcc src-file.c -lm -lpthread
The libraries referenced in this example for inclusion during linking are the math library («m») and the thread library («pthread»). They are found in /usr/lib/libm.a and /usr/lib/libpthread.a.

Note: The GNU compiler now has the command line option «-pthread» while older versions of the compiler specify the pthread library explicitly with «-lpthread». Thus now you are more likely to see gcc src-file.c -lm -pthread

Читайте также:  Как вернуть файл удаленный windows defender

How to generate a static library (object code archive file):

  • Compile: cc -Wall -c ctest1.c ctest2.c
    Compiler options:
    • -Wall: include warnings. See man page for warnings specified.
  • Create library «libctest.a»: ar -cvq libctest.a ctest1.o ctest2.o
  • List files in library: ar -t libctest.a
  • Linking with the library:
    • cc -o executable-name prog.c libctest.a
    • cc -o executable-name prog.c -L/path/to/library-directory -lctest
  • Example files:
    • ctest1.c
    • ctest2.c
    • prog.c

Historical note: After creating the library it was once necessary to run the command: ranlib ctest.a. This created a symbol table within the archive. Ranlib is now embedded into the «ar» command.

Note for MS/Windows developers: The Linux/Unix «.a» library is conceptually the same as the Visual C++ static «.lib» libraries.

How to generate a shared object: (Dynamically linked object library file.) Note that this is a two step process.

  1. Create object code
  2. Create library
  3. Optional: create default version using a symbolic link.

Library creation example: This creates the library libctest.so.1.0 and symbolic links to it.

It is also valid to cascade the linkage: If you look at the libraries in /lib/ and /usr/lib/ you will find both methodologies present. Linux developers are not consistent. What is important is that the symbolic links eventually point to an actual library.

  • -Wall: include warnings. See man page for warnings specified.
  • -fPIC: Compiler directive to output position independent code, a characteristic required by shared libraries. Also see «-fpic».
  • -shared: Produce a shared object which can then be linked with other objects to form an executable.
  • -Wl,options: Pass options to linker.
    In this example the options to be passed on to the linker are: «-soname libctest.so.1«. The name passed with the «-o» option is passed to gcc.
  • Option -o: Output of operation. In this case the name of the shared object to be output will be «libctest.so.1.0«
  • The link to /opt/lib/libctest.so allows the naming convention for the compile flag -lctest to work.
  • The link to /opt/lib/libctest.so.1 allows the run time binding to work. See dependency below.

Compile main program and link with shared object library:

Compiling for run-time linking with a dynamically linked libctest.so.1.0: Use: Where the name of the library is libctest.so. (This is why you must create the symbolic links or you will get the error «/usr/bin/ld: cannot find -lctest».)
The libraries will NOT be included in the executable but will be dynamically linked during run-time execution.

The shared library dependencies of the executable can be listed with the command: ldd name-of-executable

Example: ldd prog [Potential Pitfall] : Unresolved errors within a shared library may cause an error when the library is loaded. Example:

Error message at run-time:

The first three libraries show that there is a path resolution. The last two are problematic.

The fix is to resolve dependencies of the last two libraries when linking the library libname-of-lib.so:

  • Add the unresolved library path in /etc/ld.so.conf.d/name-of-lib-x86_64.conf and/or /etc/ld.so.conf.d/name-of-lib-i686.conf
    Reload the library cache (/etc/ld.so.cache) with the command: sudo ldconfig
    or
  • Add library and path explicitly to the compiler/linker command: -lname-of-lib -L/path/to/lib
    or
  • Add the library path to the environment variable to fix run-time dependency:
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib
  • Set path: export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
  • Run: prog

Example with code:

Using the example code above for ctest1.c, ctest2.c and prog.c

  1. Compile the library functions: gcc -Wall -fPIC -c ctest1.c ctest2.c
  2. Generate the shared library: gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 ctest1.o ctest2.o
    This generates the library libctest.so.1.0
  3. Move to lib/ directory:
    • sudo mv libctest.so.1.0 /opt/lib
    • sudo ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
    • sudo ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so

    Compile program for use with a shared library: gcc -Wall -L/opt/lib prog.c -lctest -o prog
    [Potential Pitfall] : If the symbolic links are not created (above), you will get the following error: The reference to the library name -lctest refers to /opt/lib/libctest.so

  4. Configure the library path (see below and choose one of three mechanisms).
    In this example we set the environment variable: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/lib
  5. Run the program: ./prog
    [Potential Pitfall] : You get the following error if the library path is not set:
  • gcc — GNU C compiler
  • ld — The GNU Linker
  • ldd — List library dependencies
  • ldconfig — configure dynamic linker run time bindings (update cache /etc/ld.so.cache)

In order for an executable to find the required libraries to link with during run time, one must configure the system so that the libraries can be found. Methods available: (Do at least one of the following)

    Add library directories to be included during dynamic linking to the file /etc/ld.so.conf

Add the library path to this file and then execute the command (as root) ldconfig to configure the linker run-time bindings.
You can use the «-f file-name» flag to reference another configuration file if you are developing for different environments.
See man page for command ldconfig.

Add specified directory to library cache: (as root)
ldconfig -n /opt/lib
Where /opt/lib is the directory containing your library libctest.so
(When developing and just adding your current directory: ldconfig -n . Link with -L.)

This will NOT permanently configure the system to include this directory. The information will be lost upon system reboot.

Specify the environment variable LD_LIBRARY_PATH to point to the directory paths containing the shared object library. This will specify to the run time loader that the library paths will be used during execution to resolve dependencies.
(Linux/Solaris: LD_LIBRARY_PATH, SGI: LD_LIBRARYN32_PATH, AIX: LIBPATH, Mac OS X: DYLD_LIBRARY_PATH, HP-UX: SHLIB_PATH)

Example (bash shell): export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH or add to your

This instructs the run time loader to look in the path described by the environment variable LD_LIBRARY_PATH, to resolve shared libraries. This will include the path /opt/lib.

Library paths used should conform to the «Linux Standard Base» directory structure.

ar: list object files in archive library

This will list all of the object files held in the archive library: Also see: Man page for ar

nm: list symbols: object files, archive library and shared library

The command «nm» lists symbols contained in object files:

The command «nm» lists symbols contained in the archive library:

Object symbols in static archive libraries are categorized using the source and object file hierarchy of the library:

The command «nm» lists symbols contained in the object file or shared library.

Use the command nm -D libctest.so.1.0
(or nm --dynamic libctest.so.1.0)

Note that other platforms (Cygwin) may not respond to «-D». Try nm -gC libctest.so.1.0

Also see: Man page for nm

Symbol Type Description
A The symbol’s value is absolute, and will not be changed by further linking.
B Un-initialized data section
D Initialized data section
T Normal code section
U Undefined symbol used but not defined. Dependency on another library.
W Doubly defined symbol. If found, allow definition in another library to resolve dependency.

Also see: objdump man page

readelf: list symbols in shared library

The command «readelf» command to list symbols contained in a shared library:

Use the command readelf -s /usr/lib64/libjpeg.so

Also see: readelf man page

Library versions should be specified for shared objects if the function interfaces are expected to change (C++ public/protected class definitions), more or fewer functions are included in the library, the function prototype changes (return data type (int, const int, . ) or argument list changes) or data type changes (object definitions: class data members, inheritance, virtual functions, . ).

The library version can be specified when the shared object library is created. If the library is expected to be updated, then a library version should be specified. This is especially important for shared object libraries which are dynamically linked. This also avoids the Microsoft «DLL hell» problem of conflicting libraries where a system upgrade which changes a standard library breaks an older application expecting an older version of the the shared object function.

Versioning occurs with the GNU C/C++ libraries as well. This often make binaries compiled with one version of the GNU tools incompatible with binaries compiled with other versions unless those versions also reside on the system. Multiple versions of the same library can reside on the same system due to versioning. The version of the library is included in the symbol name so the linker knows which version to link with.

One can look at the symbol version used: nm csub1.o

No version is specified in object code by default.

There is one GNU C/C++ compiler flag that explicitly deals with symbol versioning. Specify the version script to use at compile time with the flag: --version-script=your-version-script-file
Note: This is only useful when creating shared libraries. It is assumed that the programmer knows which libraries to link with when static linking. Run-time linking allows opportunity for library incompatibility.

GNU/Linux, see examples of version scripts here: sysdeps/unix/sysv/linux/Versions

Some symbols may also get version strings from assembler code which appears in glibc headers files. Look at include/libc-symbols.h.

Example: nm /lib/libc.so.6 | more

Note the use of a version script.

Library referencing a versioned library: nm /lib/libutil-2.2.5.so

These libraries are dynamically loaded / unloaded and linked during execution. Useful for creating a «plug-in» architecture.

Prototype include file for the library: ctest.h

Load and unload the library libctest.so (created above), dynamically:

gcc -rdynamic -o progdl progdl.c -ldl

  • dlopen("/opt/lib/libctest.so", RTLD_LAZY);
    Open shared library named «libctest.so«.
    The second argument indicates the binding. See include file dlfcn.h.
    Returns NULL if it fails.
    Options:
    • RTLD_LAZY: If specified, Linux is not concerned about unresolved symbols until they are referenced.
    • RTLD_NOW: All unresolved symbols resolved when dlopen() is called.
    • RTLD_GLOBAL: Make symbol libraries visible.
  • dlsym(lib_handle, "ctest1");
    Returns address to the function which has been loaded with the shared library..
    Returns NULL if it fails.
    Note: When using C++ functions, first use nm to find the «mangled» symbol name or use the extern "C" construct to avoid name mangling.
    i.e. extern "C" void function-name();

Object code location: Object code archive libraries can be located with either the executable or the loadable library. Object code routines used by both should not be duplicated in each. This is especially true for code which use static variables such as singleton classes. A static variable is global and thus can only be represented once. Including it twice will provide unexpected results. The programmer can specify that specific object code be linked with the executable by using linker commands which are passed on by the compiler.

Use the «-Wl» gcc/g++ compiler flag to pass command line arguments on to the GNU «ld» linker.

Example makefile statement: g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L -laa -Wl,--no-whole-archive $(LIBS)

  • —whole-archive: This linker directive specifies that the libraries listed following this directive (in this case AA_libs) shall be included in the resulting output even though there may not be any calls requiring its presence. This option is used to specify libraries which the loadable libraries will require at run time.
  • -no-whole-archive: This needs to be specified whether you list additional object files or not. The gcc/g++ compiler will add its own list of archive libraries and you would not want all the object code in the archive library linked in if not needed. It toggles the behavior back to normal for the rest of the archive libraries.
  • dlopen() — gain access to an executable object file
  • dclose() — close a dlopen object
  • dlsym() — obtain the address of a symbol from a dlopen object
  • dlvsym() — Programming interface to dynamic linking loader.
  • dlerror() — get diagnostic information

C++ and name mangling:

When running the above «C» examples with the «C++» compiler one will quickly find that «C++» function names get mangled and thus will not work unless the function definitions are protected with extern "C"<>.

Note that the following are not equivalent:

The following are equivalent:

Dynamic loading of C++ classes:

The dynamic library loading routines enable the programmer to load «C» functions. In C++ we would like to load class member functions. In fact the entire class may be in the library and we may want to load and have access to the entire object and all of its member functions. Do this by passing a «C» class factory function which instantiates the class.

The class «.h» file:

The class «.cpp» file:

Main executable which calls the loadable libraries:

Pitfalls:

  • The new/delete of the C++ class should both be provided by the executable or the library but not split. This is so that there is no surprise if one overloads new/delete in one or the other.

The Microsoft Windows equivalent to the Linux / Unix shared object («.so») is the «.dll». The Microsoft Windows DLL file usually has the extension «.dll», but may also use the extension «.ocx». On the old 16 bit windows, the dynamically linked libraries were also named with the «.exe» suffix. «Executing» the DLL will load it into memory.

The Visual C++ .NET IDE wizard will create a DLL framework through the GUI, and generates a «.def» file. This «module definition file» lists the functions to be exported. When exporting C++ functions, the C++ mangled names are used. Using the Visual C++ compiler to generate a «.map» file will allow you to discover the C++ mangled name to use in the «.def» file. The «SECTIONS» label in the «.def» file will define the portions which are «shared». Unfortunately the generation of DLLs are tightly coupled to the Microsoft IDE, so much so that I would not recommend trying to create one without it.

The Microsoft Windows C++ equivalent functions to libdl are the following functions:

  • ::LoadLibrary() — dlopen()
  • ::GetProcAddress() — dlsym()
  • ::FreeLibrary() — dlclose()

[Potential Pitfall] : Microsoft Visual C++ .NET compilers do not allow the linking control that the GNU linker «ld» allows (i.e. —whole-archive, -no-whole-archive). All symbols need to be resolved by the VC++ compiler for both the loadable library and the application executable individually and thus it can cause duplication of libraries when the library is loaded. This is especially bad when using static variables (i.e. used in singleton patterns) as you will get two memory locations for the static variable, one used by the loadable library and the other used by the program executable. This breaks the whole static variable concept and the singleton pattern. Thus you can not use a static variable which is referenced by by both the loadable library and the application executable as they will be unique and different. To use a unique static variable, you must pass a pointer to that static variable to the other module so that each module (main executable and DLL library) can use the same instantiation. On MS/Windows you can use shared memory or a memory mapped file so that the main executable and DLL library can share a pointer to an address they both will use.

Cross platform (Linux and MS/Windows) C++ code snippet:

Источник

Читайте также:  Как создать резервную копию линукс минт
Оцените статью