diff options
author | Damian Hobson-Garcia <dhobsong@igel.co.jp> | 2020-11-24 17:16:39 +0900 |
---|---|---|
committer | Damian Hobson-Garcia <dhobsong@igel.co.jp> | 2021-02-19 10:48:23 +0000 |
commit | f991de200799118355fd75237a740321bda7aaa7 (patch) | |
tree | 589f536d835d81f1166410e67aedb578d6e70395 | |
parent | 464ec461237b36e03c4cdb175e9d87ab502ee1d5 (diff) |
Add initial version
The initial version implements the basic functionality
of the client/server communication and lease management.
For now, one lease is created per valid connector (dependent
on CRTC availablity).
Bug-AGL: SPEC-3729
Signed-off-by: Damian Hobson-Garcia <dhobsong@igel.co.jp>
Change-Id: I2b37a892742cc22bdc53a5172c8ad3d8a7bb5e66
40 files changed, 3775 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..97c7b2c --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +BasedOnStyle: LLVM +IndentWidth: 8 +ColumnLimit: 80 +UseTab: Always +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +IndentCaseLabels: false +AlignEscapedNewlines: Left @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5368f6b --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# DRM Lease Manager + +The DRM Lease Manager uses the DRM Lease feature, introduced in the Linux kernel version 4.15, +to partition display controller output resources between multiple processes. + +For more information on the DRM lease functionality, please see the blog posts published by Keith +Packard, who developed the feature, at https://keithp.com/blogs/DRM-lease/ + +This repository contains a user space daemon to create and manage DRM leases, and distribute them +to client applications. This implementation is independent of any window system / compositor, +such as X11 or wayland, so can be used with clients that directly access a DRM device. + +This repository also provides a library that clients can use to communicate with the DRM Lease Manager. + +## Building + +Build this repository requires a recent version of the [meson](https://mesonbuild.com/Getting-meson.html) build system. + +The basic build procedure is as follows: + + meson <build_dir> + ninja -C <build_dir> + sudo ninja -C <build_dir> install + +`<build_dir>` can be any directory name, but `build` is commonly used. + +## Running + +Once installed, running the following command will start the DRM Lease Manager daemon + + drm-lease-manager [<path DRM device>] + +If no DRM device is specified, `/dev/dri/card0` will be used. +More detailed options can be displayed by specifying the `-h` flag. + +### Lease naming + +One DRM lease will be created for each connector on the DRM device (up to the number of available CRTCs). + +The names of the DRM leases will have the following pattern: + + <device>-<connector name> + +So, for example, a DRM lease for the first LVDS device on the device `/dev/dri/card0` would be named +`card0-LVDS-1`. + +## Client API usage + +The libdmclient handles all communication with the DRM Lease Manager and provides file descriptors that +can be used as if the DRM device was opened directly. Clients only need to replace their calls to +`drmOpen()` and `drmClose()` with the appropriate libdlmclient API calls. + +The client library API is described in `dlmclient.h` in the `libdlmclient` directory. + +If doxygen is available, building the repository will generate doxygen documentation in the +`<build_dir>/libdlmclient/docs/html` directory. + +### Examples + +_Error handling has been omitted for brevity and clarity of examples._ + +#### Requesting a lease from the DRM Lease Manager + +```c + struct dlm_lease *lease = dlm_get_lease("card0-HDMI-A-1"); + int drm_device_fd = dlm_lease_fd(lease); +``` + +`drm_device_fd` can now be used to access the DRM device + +#### Releasing a lease + +```c + dlm_release_lease(lease); +``` + +**Note: `drm_device_fd` is not usable after calling `dlm_release_lease()`** + +## Runtime directory +A runtime directory under the `/var` system directory is used by the drm-lease-manager and clients to +communicate with each other. +The default path is `/var/run/drm-lease-manager`, but can be changed by setting the `-Druntime_subdir` +option during configuration with `meson`. + +The runtime directory can also be specified at runtime by setting the `DLM_RUNTIME_PATH` environment variable. diff --git a/common/log.c b/common/log.c new file mode 100644 index 0000000..f3f8899 --- /dev/null +++ b/common/log.c @@ -0,0 +1,38 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log.h" + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> + +static bool debug_log = false; + +void dlm_log_enable_debug(bool enable) +{ + debug_log = enable; +} + +void dlm_log_print(bool debug, FILE *stream, char *fmt, ...) +{ + if (debug && !debug_log) + return; + + va_list argl; + va_start(argl, fmt); + vfprintf(stream, fmt, argl); + va_end(argl); +} diff --git a/common/log.h b/common/log.h new file mode 100644 index 0000000..cd0c0e3 --- /dev/null +++ b/common/log.h @@ -0,0 +1,33 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOG_H +#define LOG_H + +#include <stdbool.h> +#include <stdio.h> + +#define DEBUG_LOG(FMT, ...) \ + dlm_log_print(true, stdout, "DEBUG: %s: " FMT, __func__, ##__VA_ARGS__) +#define INFO_LOG(FMT, ...) \ + dlm_log_print(false, stdout, "INFO: " FMT, ##__VA_ARGS__) +#define WARN_LOG(FMT, ...) \ + dlm_log_print(false, stderr, "WARNING: " FMT, ##__VA_ARGS__) +#define ERROR_LOG(FMT, ...) \ + dlm_log_print(false, stderr, "ERROR: " FMT, ##__VA_ARGS__) + +void dlm_log_enable_debug(bool enable); +void dlm_log_print(bool debug, FILE *stream, char *fmt, ...); +#endif diff --git a/common/meson.build b/common/meson.build new file mode 100644 index 0000000..e465fa5 --- /dev/null +++ b/common/meson.build @@ -0,0 +1,22 @@ +libdlmcommon_sources = [ + 'socket-path.c', + 'log.c' +] + +libdlmcommon_inc = [include_directories('.')] + +if enable_tests + libdlmcommon_inc += include_directories('test') + libdlmcommon_sources += ['test/test-helpers.c'] +endif + +libdlmcommon = static_library( + 'common', + sources: libdlmcommon_sources, + include_directories : configuration_inc, +) + +dlmcommon_dep = declare_dependency( + link_with : libdlmcommon, + include_directories : libdlmcommon_inc +) diff --git a/common/socket-path.c b/common/socket-path.c new file mode 100644 index 0000000..7a412da --- /dev/null +++ b/common/socket-path.c @@ -0,0 +1,48 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "socket-path.h" +#include "config.h" +#include "log.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define RUNTIME_PATH DLM_DEFAULT_RUNTIME_PATH + +bool sockaddr_set_lease_server_path(struct sockaddr_un *sa, + const char *lease_name) +{ + int maxlen = sizeof(sa->sun_path); + char *socket_dir = getenv("DLM_RUNTIME_PATH") ?: RUNTIME_PATH; + + int len = + snprintf(sa->sun_path, maxlen, "%s/%s", socket_dir, lease_name); + + if (len < 0) { + DEBUG_LOG("Socket path creation failed: %s\n", strerror(errno)); + return false; + } + if (len >= maxlen) { + errno = ENAMETOOLONG; + DEBUG_LOG("Socket directoy path too long. " + "Full path to socket must be less than %d bytes\n", + maxlen); + return false; + } + return true; +} diff --git a/common/socket-path.h b/common/socket-path.h new file mode 100644 index 0000000..b05c60f --- /dev/null +++ b/common/socket-path.h @@ -0,0 +1,26 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SOCKET_PATH_H +#define SOCKET_PATH_H + +#include <stdbool.h> +#include <stdint.h> +#include <sys/un.h> + +bool sockaddr_set_lease_server_path(struct sockaddr_un *dest, + const char *lease_name); + +#endif diff --git a/common/test/test-helpers.c b/common/test/test-helpers.c new file mode 100644 index 0000000..ea37a89 --- /dev/null +++ b/common/test/test-helpers.c @@ -0,0 +1,56 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test-helpers.h" +#include <check.h> + +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +int get_dummy_fd(void) +{ + return dup(STDIN_FILENO); +} + +void check_fd_equality(int fd1, int fd2) +{ + struct stat s1, s2; + ck_assert_int_eq(fstat(fd1, &s1), 0); + ck_assert_int_eq(fstat(fd2, &s2), 0); + ck_assert_int_eq(s1.st_dev, s2.st_dev); + ck_assert_int_eq(s1.st_ino, s2.st_ino); +} + +void check_fd_is_open(int fd) +{ + struct stat st; + ck_assert_int_eq(fstat(fd, &st), 0); +} + +void check_fd_is_closed(int fd) +{ + struct stat st; + ck_assert_int_ne(fstat(fd, &st), 0); + ck_assert_int_eq(errno, EBADF); +} + +void check_uint_array_eq(const uint32_t *a, const uint32_t *b, int cnt) +{ + for (int i = 0; i < cnt; i++) { + ck_assert_msg(a[i] == b[i], "Array diff at index %d (%u != %u)", + i, a[i], b[i]); + } +} diff --git a/common/test/test-helpers.h b/common/test/test-helpers.h new file mode 100644 index 0000000..8886acd --- /dev/null +++ b/common/test/test-helpers.h @@ -0,0 +1,34 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_HELPERS_H +#define TEST_HELPERS_H + +#include <stdint.h> + +#define UNUSED(x) (void)(x) +#define ARRAY_LEN(x) ((int)(sizeof(x) / sizeof(x[0]))) + +/* Get a vaild fd to use a a placeholder. + * The dummy fd should never be used for anything other + * than comparing the fd value or the referenced file description. */ +int get_dummy_fd(void); + +void check_fd_equality(int fd1, int fd2); +void check_fd_is_open(int fd); +void check_fd_is_closed(int fd); + +void check_uint_array_eq(const uint32_t *a, const uint32_t *b, int cnt); +#endif diff --git a/drm-lease-manager/drm-lease.h b/drm-lease-manager/drm-lease.h new file mode 100644 index 0000000..6c618a0 --- /dev/null +++ b/drm-lease-manager/drm-lease.h @@ -0,0 +1,22 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_LEASE_H +#define DRM_LEASE_H + +struct lease_handle { + char *name; +}; +#endif diff --git a/drm-lease-manager/lease-manager.c b/drm-lease-manager/lease-manager.c new file mode 100644 index 0000000..5cfc5de --- /dev/null +++ b/drm-lease-manager/lease-manager.c @@ -0,0 +1,386 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include "lease-manager.h" + +#include "drm-lease.h" +#include "log.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +/* Number of resources, excluding planes, to be included in each DRM lease. + * Each lease needs at least a CRTC and conector. */ +#define DRM_LEASE_MIN_RES (2) + +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0])) + +struct lease { + struct lease_handle base; + + bool is_granted; + uint32_t lessee_id; + int lease_fd; + + uint32_t *object_ids; + int nobject_ids; +}; + +struct lm { + int drm_fd; + dev_t dev_id; + + drmModeResPtr drm_resource; + drmModePlaneResPtr drm_plane_resource; + uint32_t available_crtcs; + + struct lease **leases; + int nleases; +}; + +static const char *const connector_type_names[] = { + [DRM_MODE_CONNECTOR_Unknown] = "Unknown", + [DRM_MODE_CONNECTOR_VGA] = "VGA", + [DRM_MODE_CONNECTOR_DVII] = "DVI-I", + [DRM_MODE_CONNECTOR_DVID] = "DVI-D", + [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", + [DRM_MODE_CONNECTOR_Composite] = "Composite", + [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", + [DRM_MODE_CONNECTOR_LVDS] = "LVDS", + [DRM_MODE_CONNECTOR_Component] = "Component", + [DRM_MODE_CONNECTOR_9PinDIN] = "DIN", + [DRM_MODE_CONNECTOR_DisplayPort] = "DP", + [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", + [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", + [DRM_MODE_CONNECTOR_TV] = "TV", + [DRM_MODE_CONNECTOR_eDP] = "eDP", + [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", + [DRM_MODE_CONNECTOR_DSI] = "DSI", + [DRM_MODE_CONNECTOR_DPI] = "DPI", + [DRM_MODE_CONNECTOR_WRITEBACK] = "Writeback", +}; + +static char *drm_create_lease_name(struct lm *lm, drmModeConnectorPtr connector) +{ + uint32_t type = connector->connector_type; + uint32_t id = connector->connector_type_id; + + if (type >= ARRAY_LENGTH(connector_type_names)) + type = DRM_MODE_CONNECTOR_Unknown; + + /* If the type is "Unknown", use the connector_id as the identify to + * guarantee that the name will be unique. */ + if (type == DRM_MODE_CONNECTOR_Unknown) + id = connector->connector_id; + + char *name; + if (asprintf(&name, "card%d-%s-%d", minor(lm->dev_id), + connector_type_names[type], id) < 0) + return NULL; + + return name; +} + +static int drm_get_encoder_crtc_index(struct lm *lm, drmModeEncoderPtr encoder) +{ + uint32_t crtc_id = encoder->crtc_id; + if (!crtc_id) + return -1; + + // The CRTC index only makes sense if it is less than the number of + // bits in the encoder possible_crtcs bitmap, which is 32. + assert(lm->drm_resource->count_crtcs < 32); + + for (int i = 0; i < lm->drm_resource->count_crtcs; i++) { + if (lm->drm_resource->crtcs[i] == crtc_id) + return i; + } + return -1; +} + +static int drm_get_active_crtc_index(struct lm *lm, + drmModeConnectorPtr connector) +{ + drmModeEncoder *encoder = + drmModeGetEncoder(lm->drm_fd, connector->encoder_id); + if (!encoder) + return -1; + + int crtc_idx = drm_get_encoder_crtc_index(lm, encoder); + drmModeFreeEncoder(encoder); + return crtc_idx; +} + +static int drm_get_crtc_index(struct lm *lm, drmModeConnectorPtr connector) +{ + + // try the active CRTC first + int crtc_index = drm_get_active_crtc_index(lm, connector); + if (crtc_index != -1) + return crtc_index; + + // If not try the first available CRTC on the connector/encoder + for (int i = 0; i < connector->count_encoders; i++) { + drmModeEncoder *encoder = + drmModeGetEncoder(lm->drm_fd, connector->encoders[i]); + + assert(encoder); + + uint32_t usable_crtcs = + lm->available_crtcs & encoder->possible_crtcs; + int crtc = ffs(usable_crtcs); + drmModeFreeEncoder(encoder); + if (crtc == 0) + continue; + crtc_index = crtc - 1; + lm->available_crtcs &= ~(1 << crtc_index); + break; + } + return crtc_index; +} + +static void drm_find_available_crtcs(struct lm *lm) +{ + // Assume all CRTCS are available by default, + lm->available_crtcs = ~0; + + // then remove any that are in use. */ + for (int i = 0; i < lm->drm_resource->count_encoders; i++) { + int enc_id = lm->drm_resource->encoders[i]; + drmModeEncoderPtr enc = drmModeGetEncoder(lm->drm_fd, enc_id); + if (!enc) + continue; + + int crtc_idx = drm_get_encoder_crtc_index(lm, enc); + if (crtc_idx >= 0) + lm->available_crtcs &= ~(1 << crtc_idx); + + drmModeFreeEncoder(enc); + } +} + +static bool lease_add_planes(struct lm *lm, struct lease *lease, int crtc_index) +{ + for (uint32_t i = 0; i < lm->drm_plane_resource->count_planes; i++) { + uint32_t plane_id = lm->drm_plane_resource->planes[i]; + drmModePlanePtr plane = drmModeGetPlane(lm->drm_fd, plane_id); + + assert(plane); + + // Exclude planes that can be used with multiple CRTCs for now + if (plane->possible_crtcs == (1u << crtc_index)) { + lease->object_ids[lease->nobject_ids++] = plane_id; + } + drmModeFreePlane(plane); + } + return true; +} + +static void lease_free(struct lease *lease) +{ + free(lease->base.name); + free(lease->object_ids); + free(lease); +} + +static struct lease *lease_create(struct lm *lm, drmModeConnectorPtr connector) +{ + struct lease *lease = calloc(1, sizeof(struct lease)); + if (!lease) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + return NULL; + } + + lease->base.name = drm_create_lease_name(lm, connector); + if (!lease->base.name) { + DEBUG_LOG("Can't create lease name: %s\n", strerror(errno)); + goto err; + } + + int nobjects = lm->drm_plane_resource->count_planes + DRM_LEASE_MIN_RES; + lease->object_ids = calloc(nobjects, sizeof(uint32_t)); + if (!lease->object_ids) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + goto err; + } + + int crtc_index = drm_get_crtc_index(lm, connector); + if (crtc_index < 0) { + DEBUG_LOG("No crtc found for connector: %s\n", + lease->base.name); + goto err; + } + + if (!lease_add_planes(lm, lease, crtc_index)) + goto err; + + uint32_t crtc_id = lm->drm_resource->crtcs[crtc_index]; + lease->object_ids[lease->nobject_ids++] = crtc_id; + lease->object_ids[lease->nobject_ids++] = connector->connector_id; + + lease->is_granted = false; + + return lease; + +err: + lease_free(lease); + return NULL; +} + +struct lm *lm_create(const char *device) +{ + struct lm *lm = calloc(1, sizeof(struct lm)); + if (!lm) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + return NULL; + } + lm->drm_fd = open(device, O_RDWR); + if (lm->drm_fd < 0) { + ERROR_LOG("Cannot open DRM device (%s): %s\n", device, + strerror(errno)); + goto err; + } + + lm->drm_resource = drmModeGetResources(lm->drm_fd); + if (!lm->drm_resource) { + ERROR_LOG("Invalid DRM device(%s)\n", device); + DEBUG_LOG("drmModeGetResources failed: %s\n", strerror(errno)); + goto err; + } + + lm->drm_plane_resource = drmModeGetPlaneResources(lm->drm_fd); + if (!lm->drm_plane_resource) { + DEBUG_LOG("drmModeGetPlaneResources failed: %s\n", + strerror(errno)); + goto err; + } + + struct stat st; + if (fstat(lm->drm_fd, &st) < 0 || !S_ISCHR(st.st_mode)) { + DEBUG_LOG("%s is not a valid device file\n", device); + goto err; + } + + lm->dev_id = st.st_rdev; + + int num_leases = lm->drm_resource->count_connectors; + + lm->leases = calloc(num_leases, sizeof(struct lease *)); + if (!lm->leases) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + goto err; + } + + drm_find_available_crtcs(lm); + + for (int i = 0; i < num_leases; i++) { + uint32_t connector_id = lm->drm_resource->connectors[i]; + drmModeConnectorPtr connector = + drmModeGetConnector(lm->drm_fd, connector_id); + + if (!connector) + continue; + + struct lease *lease = lease_create(lm, connector); + drmModeFreeConnector(connector); + + if (!lease) + continue; + + lm->leases[lm->nleases] = lease; + lm->nleases++; + } + if (lm->nleases == 0) + goto err; + + return lm; + +err: + lm_destroy(lm); + return NULL; +} + +void lm_destroy(struct lm *lm) +{ + assert(lm); + + for (int i = 0; i < lm->nleases; i++) { + lm_lease_revoke(lm, (struct lease_handle *)lm->leases[i]); + lease_free(lm->leases[i]); + } + + free(lm->leases); + drmModeFreeResources(lm->drm_resource); + drmModeFreePlaneResources(lm->drm_plane_resource); + close(lm->drm_fd); + free(lm); +} + +int lm_get_lease_handles(struct lm *lm, struct lease_handle ***handles) +{ + assert(lm); + assert(handles); + + *handles = (struct lease_handle **)lm->leases; + return lm->nleases; +} + +int lm_lease_grant(struct lm *lm, struct lease_handle *handle) +{ + assert(lm); + assert(handle); + + struct lease *lease = (struct lease *)handle; + if (lease->is_granted) + return lease->lease_fd; + + lease->lease_fd = + drmModeCreateLease(lm->drm_fd, lease->object_ids, + lease->nobject_ids, 0, &lease->lessee_id); + if (lease->lease_fd < 0) { + ERROR_LOG("drmModeCreateLease failed on lease %s: %s\n", + lease->base.name, strerror(errno)); + return -1; + } + + lease->is_granted = true; + return lease->lease_fd; +} + +void lm_lease_revoke(struct lm *lm, struct lease_handle *handle) +{ + assert(lm); + assert(handle); + + struct lease *lease = (struct lease *)handle; + + if (!lease->is_granted) + return; + + drmModeRevokeLease(lm->drm_fd, lease->lessee_id); + close(lease->lease_fd); + lease->is_granted = false; +} diff --git a/drm-lease-manager/lease-manager.h b/drm-lease-manager/lease-manager.h new file mode 100644 index 0000000..ed5bcdc --- /dev/null +++ b/drm-lease-manager/lease-manager.h @@ -0,0 +1,29 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LEASE_MANAGER_H +#define LEASE_MANAGER_H +#include "drm-lease.h" + +struct lm; + +struct lm *lm_create(const char *path); +void lm_destroy(struct lm *lm); + +int lm_get_lease_handles(struct lm *lm, struct lease_handle ***lease_handles); + +int lm_lease_grant(struct lm *lm, struct lease_handle *lease_handle); +void lm_lease_revoke(struct lm *lm, struct lease_handle *lease_handle); +#endif diff --git a/drm-lease-manager/lease-server.c b/drm-lease-manager/lease-server.c new file mode 100644 index 0000000..e05e8e4 --- /dev/null +++ b/drm-lease-manager/lease-server.c @@ -0,0 +1,335 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lease-server.h" +#include "log.h" +#include "socket-path.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <sys/epoll.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#define SOCK_LOCK_SUFFIX ".lock" + +struct ls_socket { + int fd; + struct ls_server *serv; +}; + +struct ls_server { + struct lease_handle *lease_handle; + struct sockaddr_un address; + int server_socket_lock; + + struct ls_socket listen; + struct ls_socket client; + + bool is_client_connected; +}; + +struct ls { + int epoll_fd; + + struct ls_server *servers; + int nservers; +}; + +static bool client_connect(struct ls *ls, struct ls_server *serv) +{ + int cfd = accept(serv->listen.fd, NULL, NULL); + if (cfd < 0) { + DEBUG_LOG("accept failed on %s: %s\n", serv->address.sun_path, + strerror(errno)); + return false; + } + + if (serv->is_client_connected) { + WARN_LOG("Client already connected on %s\n", + serv->address.sun_path); + close(cfd); + return false; + } + + serv->client.fd = cfd; + serv->client.serv = serv; + + struct epoll_event ev = { + .events = POLLHUP, + .data.ptr = &serv->client, + }; + if (epoll_ctl(ls->epoll_fd, EPOLL_CTL_ADD, cfd, &ev)) { + DEBUG_LOG("epoll_ctl add failed: %s\n", strerror(errno)); + close(cfd); + return false; + } + + serv->is_client_connected = true; + return true; +} + +static int create_socket_lock(struct sockaddr_un *addr) +{ + int lock_fd; + + int lockfile_len = sizeof(addr->sun_path) + sizeof(SOCK_LOCK_SUFFIX); + char lockfile[lockfile_len]; + int len = snprintf(lockfile, lockfile_len, "%s%s", addr->sun_path, + SOCK_LOCK_SUFFIX); + + if (len < 0 || len >= lockfile_len) { + DEBUG_LOG("Can't create socket lock filename\n"); + return -1; + } + + lock_fd = open(lockfile, O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + if (lock_fd < 0) { + ERROR_LOG("Cannot access runtime directory\n"); + return -1; + } + + if (flock(lock_fd, LOCK_EX | LOCK_NB)) { + ERROR_LOG( + "socket %s: in use. Possible duplicate lease name or " + "mutiple drm-lease-manager instances running\n", + addr->sun_path); + close(lock_fd); + return -1; + } + + return lock_fd; +} + +static bool server_setup(struct ls *ls, struct ls_server *serv, + struct lease_handle *lease_handle) +{ + struct sockaddr_un *address = &serv->address; + + if (!sockaddr_set_lease_server_path(address, lease_handle->name)) + return false; + + int socket_lock = create_socket_lock(address); + if (socket_lock < 0) + return false; + + /* The socket address is now owned by this instance, so any existing + * sockets can safely be removed */ + unlink(address->sun_path); + + address->sun_family = AF_UNIX; + + int server_socket = socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (server_socket < 0) { + DEBUG_LOG("Socket creation failed: %s\n", strerror(errno)); + return false; + } + + if (bind(server_socket, (struct sockaddr *)address, sizeof(*address))) { + ERROR_LOG("Failed to create named socket at %s: %s\n", + address->sun_path, strerror(errno)); + close(server_socket); + return false; + } + + if (listen(server_socket, 0)) { + DEBUG_LOG("listen failed on %s: %s\n", address->sun_path, + strerror(errno)); + close(server_socket); + unlink(address->sun_path); + return false; + } + + serv->is_client_connected = false; + serv->lease_handle = lease_handle; + serv->server_socket_lock = socket_lock; + serv->listen.fd = server_socket; + serv->listen.serv = serv; + + struct epoll_event ev = { + .events = POLLIN, + .data.ptr = &serv->listen, + }; + + if (epoll_ctl(ls->epoll_fd, EPOLL_CTL_ADD, server_socket, &ev)) { + DEBUG_LOG("epoll_ctl add failed: %s\n", strerror(errno)); + close(server_socket); + unlink(address->sun_path); + return false; + } + + INFO_LOG("Lease server (%s) initialized at %s\n", lease_handle->name, + address->sun_path); + return true; +} + +static void server_shutdown(struct ls *ls, struct ls_server *serv) +{ + if (unlink(serv->address.sun_path)) { + WARN_LOG("Server socket %s delete failed: %s\n", + serv->address.sun_path, strerror(errno)); + } + + epoll_ctl(ls->epoll_fd, EPOLL_CTL_DEL, serv->listen.fd, NULL); + close(serv->listen.fd); + ls_disconnect_client(ls, serv); + close(serv->server_socket_lock); +} + +struct ls *ls_create(struct lease_handle **lease_handles, int count) +{ + assert(lease_handles); + assert(count > 0); + + struct ls *ls = calloc(1, sizeof(struct ls)); + if (!ls) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + return NULL; + } + + ls->servers = calloc(count, sizeof(struct ls_server)); + if (!ls->servers) { + DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); + goto err; + } + + ls->epoll_fd = epoll_create1(0); + if (ls->epoll_fd < 0) { + DEBUG_LOG("epoll_create failed: %s\n", strerror(errno)); + goto err; + } + + for (int i = 0; i < count; i++) { + if (!server_setup(ls, &ls->servers[i], lease_handles[i])) + goto err; + ls->nservers++; + } + return ls; +err: + ls_destroy(ls); + return NULL; +} + +void ls_destroy(struct ls *ls) +{ + assert(ls); + + for (int i = 0; i < ls->nservers; i++) + server_shutdown(ls, &ls->servers[i]); + + close(ls->epoll_fd); + free(ls->servers); + free(ls); +} + +bool ls_get_request(struct ls *ls, struct ls_req *req) +{ + assert(ls); + assert(req); + + int request = -1; + while (request < 0) { + struct epoll_event ev; + if (epoll_wait(ls->epoll_fd, &ev, 1, -1) < 0) { + if (errno == EINTR) + continue; + DEBUG_LOG("epoll_wait failed: %s\n", strerror(errno)); + return false; + } + + struct ls_socket *sock = ev.data.ptr; + assert(sock); + + struct ls_server *server = sock->serv; + req->lease_handle = server->lease_handle; + req->server = server; + + if (sock == &server->listen) { + if (!(ev.events & POLLIN)) + continue; + if (client_connect(ls, server)) + request = LS_REQ_GET_LEASE; + } else if (sock == &server->client) { + if (!(ev.events & POLLHUP)) + continue; + request = LS_REQ_RELEASE_LEASE; + } else { + ERROR_LOG("Internal error: Invalid socket context\n"); + return false; + } + } + req->type = request; + return true; +} + +bool ls_send_fd(struct ls *ls, struct ls_server *serv, int fd) +{ + assert(ls); + assert(serv); + + if (fd < 0) + return false; + + char data[1]; + struct iovec iov = { + .iov_base = data, + .iov_len = sizeof(data), + }; + + char ctrl_buf[CMSG_SPACE(sizeof(int))] = {0}; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_controllen = sizeof(ctrl_buf), + .msg_control = ctrl_buf, + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *)CMSG_DATA(cmsg)) = fd; + + if (sendmsg(serv->client.fd, &msg, 0) < 0) { + DEBUG_LOG("sendmsg failed on %s: %s\n", serv->address.sun_path, + strerror(errno)); + return false; + } + + INFO_LOG("Lease request granted on %s\n", serv->address.sun_path); + return true; +} + +void ls_disconnect_client(struct ls *ls, struct ls_server *serv) +{ + assert(ls); + assert(serv); + if (!serv->is_client_connected) + return; + + epoll_ctl(ls->epoll_fd, EPOLL_CTL_DEL, serv->client.fd, NULL); + close(serv->client.fd); + serv->is_client_connected = false; +} diff --git a/drm-lease-manager/lease-server.h b/drm-lease-manager/lease-server.h new file mode 100644 index 0000000..c87b834 --- /dev/null +++ b/drm-lease-manager/lease-server.h @@ -0,0 +1,42 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LEASE_SERVER_H +#define LEASE_SERVER_H +#include <stdbool.h> + +#include "drm-lease.h" + +struct ls; +struct ls_server; +enum ls_req_type { + LS_REQ_GET_LEASE, + LS_REQ_RELEASE_LEASE, +}; + +struct ls_req { + struct lease_handle *lease_handle; + struct ls_server *server; + enum ls_req_type type; +}; + +struct ls *ls_create(struct lease_handle **lease_handles, int count); +void ls_destroy(struct ls *ls); + +bool ls_get_request(struct ls *ls, struct ls_req *req); +bool ls_send_fd(struct ls *ls, struct ls_server *server, int fd); + +void ls_disconnect_client(struct ls *ls, struct ls_server *server); +#endif diff --git a/drm-lease-manager/main.c b/drm-lease-manager/main.c new file mode 100644 index 0000000..c3a933f --- /dev/null +++ b/drm-lease-manager/main.c @@ -0,0 +1,120 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lease-manager.h" +#include "lease-server.h" +#include "log.h" + +#include <assert.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> + +static void usage(const char *progname) +{ + printf("Usage: %s [OPTIONS] [<DRM device>]\n\n" + "Options:\n" + "-h, --help \tPrint this help\n" + "-v, --verbose \tEnable verbose debug messages\n", + progname); +} + +const char *opts = "vh"; +const struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0}, +}; + +int main(int argc, char **argv) +{ + char *device = "/dev/dri/card0"; + + bool debug_log = false; + + int c; + while ((c = getopt_long(argc, argv, opts, options, NULL)) != -1) { + int ret = EXIT_FAILURE; + switch (c) { + case 'v': + debug_log = true; + break; + case 'h': + ret = EXIT_SUCCESS; + /* fall through */ + default: + usage(argv[0]); + return ret; + } + } + + if (optind < argc) + device = argv[optind]; + + dlm_log_enable_debug(debug_log); + + struct lm *lm = lm_create(device); + if (!lm) { + ERROR_LOG("DRM Lease initialization failed\n"); + return EXIT_FAILURE; + } + + struct lease_handle **lease_handles = NULL; + int count_ids = lm_get_lease_handles(lm, &lease_handles); + assert(count_ids > 0); + + struct ls *ls = ls_create(lease_handles, count_ids); + if (!ls) { + lm_destroy(lm); + ERROR_LOG("Client socket initialization failed\n"); + return EXIT_FAILURE; + } + + struct ls_req req; + while (ls_get_request(ls, &req)) { + switch (req.type) { + case LS_REQ_GET_LEASE: { + int fd = lm_lease_grant(lm, req.lease_handle); + if (fd < 0) { + ERROR_LOG( + "Can't fulfill lease request: lease=%s\n", + req.lease_handle->name); + ls_disconnect_client(ls, req.server); + break; + } + + if (!ls_send_fd(ls, req.server, fd)) { + ERROR_LOG( + "Client communication error: lease=%s\n", + req.lease_handle->name); + ls_disconnect_client(ls, req.server); + lm_lease_revoke(lm, req.lease_handle); + } + break; + } + case LS_REQ_RELEASE_LEASE: + ls_disconnect_client(ls, req.server); + lm_lease_revoke(lm, req.lease_handle); + break; + default: + ERROR_LOG("Internal error: Invalid lease request\n"); + goto done; + } + } +done: + ls_destroy(ls); + lm_destroy(lm); + return EXIT_FAILURE; +} diff --git a/drm-lease-manager/meson.build b/drm-lease-manager/meson.build new file mode 100644 index 0000000..1171b02 --- /dev/null +++ b/drm-lease-manager/meson.build @@ -0,0 +1,12 @@ + +lease_manager_files = files('lease-manager.c') +lease_server_files = files('lease-server.c') +main = executable('drm-lease-manager', + [ 'main.c', lease_manager_files, lease_server_files ], + dependencies: [ drm_dep, dlmcommon_dep ], + install: true, +) + +if enable_tests + subdir('test') +endif diff --git a/drm-lease-manager/test/lease-manager-test.c b/drm-lease-manager/test/lease-manager-test.c new file mode 100644 index 0000000..c741cb3 --- /dev/null +++ b/drm-lease-manager/test/lease-manager-test.c @@ -0,0 +1,448 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <check.h> +#include <fff.h> + +#include <stdlib.h> +#include <unistd.h> +#include <xf86drmMode.h> + +#include "lease-manager.h" +#include "log.h" +#include "test-drm-device.h" +#include "test-helpers.h" + +/* CHECK_LEASE_OBJECTS + * + * Checks the list of objects associated with a given lease_index. + * Asks the lease manager to create the lease, and checks that + * the requested objects are the ones given in the supplied list. */ + +#define CHECK_LEASE_OBJECTS(lease, ...) \ + do { \ + lm_lease_grant(lm, lease); \ + uint32_t objs[] = {__VA_ARGS__}; \ + int nobjs = ARRAY_LEN(objs); \ + ck_assert_int_eq(drmModeCreateLease_fake.arg2_val, nobjs); \ + check_uint_array_eq(drmModeCreateLease_fake.arg1_val, objs, \ + nobjs); \ + } while (0) + +/************** Mock functions *************/ +DEFINE_FFF_GLOBALS; + +FAKE_VALUE_FUNC(drmModeResPtr, drmModeGetResources, int); +FAKE_VOID_FUNC(drmModeFreeResources, drmModeResPtr); +FAKE_VALUE_FUNC(drmModePlaneResPtr, drmModeGetPlaneResources, int); +FAKE_VOID_FUNC(drmModeFreePlaneResources, drmModePlaneResPtr); + +FAKE_VALUE_FUNC(drmModePlanePtr, drmModeGetPlane, int, uint32_t); +FAKE_VOID_FUNC(drmModeFreePlane, drmModePlanePtr); +FAKE_VALUE_FUNC(drmModeConnectorPtr, drmModeGetConnector, int, uint32_t); +FAKE_VOID_FUNC(drmModeFreeConnector, drmModeConnectorPtr); +FAKE_VALUE_FUNC(drmModeEncoderPtr, drmModeGetEncoder, int, uint32_t); +FAKE_VOID_FUNC(drmModeFreeEncoder, drmModeEncoderPtr); + +FAKE_VALUE_FUNC(int, drmModeCreateLease, int, const uint32_t *, int, int, + uint32_t *); +FAKE_VALUE_FUNC(int, drmModeRevokeLease, int, uint32_t); + +/************** Test fixutre functions *************************/ + +static void test_setup(void) +{ + RESET_FAKE(drmModeGetResources); + RESET_FAKE(drmModeFreeResources); + RESET_FAKE(drmModeGetPlaneResources); + RESET_FAKE(drmModeFreePlaneResources); + + RESET_FAKE(drmModeGetPlane); + RESET_FAKE(drmModeFreePlane); + RESET_FAKE(drmModeGetConnector); + RESET_FAKE(drmModeFreeConnector); + RESET_FAKE(drmModeGetEncoder); + RESET_FAKE(drmModeFreeEncoder); + + RESET_FAKE(drmModeCreateLease); + RESET_FAKE(drmModeRevokeLease); + + drmModeGetResources_fake.return_val = TEST_DEVICE_RESOURCES; + drmModeGetPlaneResources_fake.return_val = TEST_DEVICE_PLANE_RESOURCES; + + drmModeGetPlane_fake.custom_fake = get_plane; + drmModeGetConnector_fake.custom_fake = get_connector; + drmModeGetEncoder_fake.custom_fake = get_encoder; + drmModeCreateLease_fake.custom_fake = create_lease; +} + +static void test_shutdown(void) +{ + reset_drm_test_device(); +} + +/************** Resource enumeration tests *************/ + +/* These tests verify that the lease manager correctly assigns + * DRM resources to thier respective leases. In some cases + * the lease manager must choose which resources to include in + * each lease, so these tests verify that a valid (but not + * necessarily optimal) choice is made. + */ + +/* all_outputs_connected + * + * Test details: Create leases when all crtc/encoder/connector paths are + * connected. + * + * Expected results: Leases are created for the currently connected sets of + * resources. + */ +START_TEST(all_outputs_connected) +{ + int out_cnt = 2, plane_cnt = 0; + + ck_assert_int_eq( + setup_drm_test_device(out_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), ENCODER_ID(0), &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), ENCODER_ID(1), &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), CRTC_ID(0), 0x3), + ENCODER(ENCODER_ID(1), CRTC_ID(1), 0x2), + }; + + setup_test_device_layout(connectors, encoders, NULL); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(out_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], CRTC_ID(0), CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], CRTC_ID(1), CONNECTOR_ID(1)); + + lm_destroy(lm); +} +END_TEST + +/* no_outputs_connected + * + * Test details: Create leases when no crtc/encoder/connector paths are + * connected. + * + * Expected results: Available resources are divided between the leases. + * The same resource should not appear in multiple leases. + */ +START_TEST(no_outputs_connected) +{ + int out_cnt = 2, plane_cnt = 0; + + ck_assert_int_eq( + setup_drm_test_device(out_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), 0, &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), 0, &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), 0, 0x2), + ENCODER(ENCODER_ID(1), 0, 0x3), + }; + + setup_test_device_layout(connectors, encoders, NULL); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(out_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], CRTC_ID(1), CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], CRTC_ID(0), CONNECTOR_ID(1)); + + lm_destroy(lm); +} +END_TEST + +/* some_outputs_connected */ +/* Test details: Create leases when one output is connected and one is not. + * Expected results: Currently connected resources should be added to + * the same lease. + * The non-connected resources should be added to a second + * lease. + */ +START_TEST(some_outputs_connected) +{ + int out_cnt = 2, plane_cnt = 0; + + ck_assert_int_eq( + setup_drm_test_device(out_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), ENCODER_ID(0), &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), 0, &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), CRTC_ID(0), 0x3), + ENCODER(ENCODER_ID(1), 0, 0x3), + }; + + setup_test_device_layout(connectors, encoders, NULL); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(out_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], CRTC_ID(0), CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], CRTC_ID(1), CONNECTOR_ID(1)); + + lm_destroy(lm); +} +END_TEST + +/* fewer_crtcs_than_connectors */ +/* Test details: Create leases on a system with more connectors than CRTCs + * Expected results: Number of leases generated should correspond to number of + * CRTCs. + * Leases contain one valid connector for each CRTC. + */ +START_TEST(fewer_crtcs_than_connectors) +{ + int out_cnt = 3, plane_cnt = 0, crtc_cnt = 2; + + ck_assert_int_eq( + setup_drm_test_device(crtc_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), 0, &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), 0, &ENCODER_ID(1), 1), + CONNECTOR(CONNECTOR_ID(2), 0, &ENCODER_ID(2), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), 0, 0x3), + ENCODER(ENCODER_ID(1), 0, 0x1), + ENCODER(ENCODER_ID(2), 0, 0x3), + }; + + setup_test_device_layout(connectors, encoders, NULL); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(lm_get_lease_handles(lm, &handles), crtc_cnt); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], CRTC_ID(0), CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], CRTC_ID(1), CONNECTOR_ID(2)); + lm_destroy(lm); +} +END_TEST + +/* separate_overlay_planes_by_crtc */ +/* Test details: Add overlay planes to leases. Each plane is tied to a + * specific CRTC. + * Expected results: The leases contain all of the planes for connected to + * each CRTC and no others. + */ +START_TEST(separate_overlay_planes_by_crtc) +{ + + int out_cnt = 2, plane_cnt = 3; + + ck_assert_int_eq( + setup_drm_test_device(out_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), ENCODER_ID(0), &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), ENCODER_ID(1), &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), CRTC_ID(0), 0x1), + ENCODER(ENCODER_ID(1), CRTC_ID(1), 0x2), + }; + + drmModePlane planes[] = { + PLANE(PLANE_ID(0), 0x2), + PLANE(PLANE_ID(1), 0x1), + PLANE(PLANE_ID(2), 0x2), + }; + + setup_test_device_layout(connectors, encoders, planes); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(out_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], PLANE_ID(1), CRTC_ID(0), + CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], PLANE_ID(0), PLANE_ID(2), CRTC_ID(1), + CONNECTOR_ID(1)); + lm_destroy(lm); +} +END_TEST + +/* reject_planes_shared_between_multiple_crtcs */ +/* Test details: Add overlay planes to leases. Some planes are shared between + * multiple CRTCs. + * Expected results: The leases contain all of the unique planes for each CRTC. + * Planes that can be used on multiple CRTCs are not included + * in any lease. + */ +START_TEST(reject_planes_shared_between_multiple_crtcs) +{ + + int out_cnt = 2, plane_cnt = 3; + + ck_assert_int_eq( + setup_drm_test_device(out_cnt, out_cnt, out_cnt, plane_cnt), true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), ENCODER_ID(0), &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), ENCODER_ID(1), &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), CRTC_ID(0), 0x1), + ENCODER(ENCODER_ID(1), CRTC_ID(1), 0x2), + }; + + drmModePlane planes[] = { + PLANE(PLANE_ID(0), 0x2), + PLANE(PLANE_ID(1), 0x1), + PLANE(PLANE_ID(2), 0x3), + }; + + setup_test_device_layout(connectors, encoders, planes); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(out_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + CHECK_LEASE_OBJECTS(handles[0], PLANE_ID(1), CRTC_ID(0), + CONNECTOR_ID(0)); + CHECK_LEASE_OBJECTS(handles[1], PLANE_ID(0), CRTC_ID(1), + CONNECTOR_ID(1)); + lm_destroy(lm); +} +END_TEST + +static void add_connector_enum_tests(Suite *s) +{ + TCase *tc = tcase_create("Resource enumeration"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, all_outputs_connected); + tcase_add_test(tc, no_outputs_connected); + tcase_add_test(tc, fewer_crtcs_than_connectors); + tcase_add_test(tc, some_outputs_connected); + tcase_add_test(tc, separate_overlay_planes_by_crtc); + tcase_add_test(tc, reject_planes_shared_between_multiple_crtcs); + suite_add_tcase(s, tc); +} + +/************** Lease management tests *************/ + +/* create_and_revoke_lease */ +/* Test details: Create leases and revoke them. + * Expected results: drmModeRevokeLease() is called with the correct leasee_id. + */ +START_TEST(create_and_revoke_lease) +{ + int lease_cnt = 2; + bool res = setup_drm_test_device(lease_cnt, lease_cnt, lease_cnt, 0); + ck_assert_int_eq(res, true); + + drmModeConnector connectors[] = { + CONNECTOR(CONNECTOR_ID(0), ENCODER_ID(0), &ENCODER_ID(0), 1), + CONNECTOR(CONNECTOR_ID(1), ENCODER_ID(1), &ENCODER_ID(1), 1), + }; + + drmModeEncoder encoders[] = { + ENCODER(ENCODER_ID(0), CRTC_ID(0), 0x1), + ENCODER(ENCODER_ID(1), CRTC_ID(1), 0x2), + }; + + setup_test_device_layout(connectors, encoders, NULL); + + struct lm *lm = lm_create(TEST_DRM_DEVICE); + ck_assert_ptr_ne(lm, NULL); + + struct lease_handle **handles; + ck_assert_int_eq(lease_cnt, lm_get_lease_handles(lm, &handles)); + ck_assert_ptr_ne(handles, NULL); + + for (int i = 0; i < lease_cnt; i++) { + ck_assert_int_ge(lm_lease_grant(lm, handles[i]), 0); + lm_lease_revoke(lm, handles[i]); + } + + ck_assert_int_eq(drmModeRevokeLease_fake.call_count, lease_cnt); + + for (int i = 0; i < lease_cnt; i++) { + ck_assert_int_eq(drmModeRevokeLease_fake.arg1_history[i], + LESSEE_ID(i)); + } +} +END_TEST + +static void add_lease_management_tests(Suite *s) +{ + TCase *tc = tcase_create("Lease management"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, create_and_revoke_lease); + suite_add_tcase(s, tc); +} + +int main(void) +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = suite_create("DLM lease manager tests"); + + add_connector_enum_tests(s); + add_lease_management_tests(s); + + sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/drm-lease-manager/test/lease-server-test.c b/drm-lease-manager/test/lease-server-test.c new file mode 100644 index 0000000..f8e000e --- /dev/null +++ b/drm-lease-manager/test/lease-server-test.c @@ -0,0 +1,380 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <check.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pthread.h> + +#include "lease-server.h" +#include "log.h" +#include "test-helpers.h" +#include "test-socket-client.h" + +#define SOCKETDIR "/tmp" + +/************** Test fixutre functions *************************/ +struct test_config default_test_config; + +#define TEST_LEASE_NAME "test-lease" + +static struct lease_handle test_lease = { + .name = TEST_LEASE_NAME, +}; + +static void test_setup(void) +{ + dlm_log_enable_debug(true); + setenv("DLM_RUNTIME_PATH", SOCKETDIR, 1); + + default_test_config = (struct test_config){ + .lease = &test_lease, + }; +} + +static void test_shutdown(void) +{ + test_config_cleanup(&default_test_config); +} + +static struct ls *create_default_server(void) +{ + struct lease_handle *leases[] = { + &test_lease, + }; + struct ls *ls = ls_create(leases, 1); + ck_assert_ptr_ne(ls, NULL); + return ls; +} + +/************** Lease server error handling tests *************/ + +/* duplicate_server_failure + * + * Test details: Try to intialize the same server twice + * Expected results: ls_create() fails. + */ +START_TEST(duplicate_server_failure) +{ + struct lease_handle *leases[] = {&test_lease, &test_lease}; + struct ls *ls = ls_create(leases, 2); + ck_assert_ptr_eq(ls, NULL); +} +END_TEST + +START_TEST(long_lease_name_failure) +{ + char long_lease_name[200]; + + size_t len = sizeof(long_lease_name) - 1; + memset(long_lease_name, 'a', len); + long_lease_name[len] = '\0'; + + struct lease_handle long_name_lease = {.name = long_lease_name}; + + struct lease_handle *leases[] = {&long_name_lease}; + struct ls *ls = ls_create(leases, 1); + ck_assert_ptr_eq(ls, NULL); +} +END_TEST + +static void add_error_tests(Suite *s) +{ + TCase *tc = tcase_create("Lease server errors"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, duplicate_server_failure); + tcase_add_test(tc, long_lease_name_failure); + suite_add_tcase(s, tc); +} + +/************** Client request handling tests ************/ + +/* Test the handling of client requests. Make sure that the + * proper struct ls_req are generated for each client request. + */ + +static void check_request(struct ls_req *req, + struct lease_handle *expected_lease, + enum ls_req_type expected_type) +{ + ck_assert_ptr_eq(req->lease_handle, expected_lease); + ck_assert_int_eq(req->type, expected_type); +} + +static void get_and_check_request(struct ls *ls, + struct lease_handle *expected_lease, + enum ls_req_type expected_type) +{ + struct ls_req req; + bool req_valid = ls_get_request(ls, &req); + ck_assert_int_eq(req_valid, true); + check_request(&req, expected_lease, expected_type); +} + +/* Asynchronous version of the above. Has the extra overhead of + * spawning a new thread, so should be used sparingly. */ +struct async_req { + pthread_t tid; + struct ls *ls; + + bool req_valid; + struct ls_req expected; + struct ls_req actual; +}; + +static void *get_request_thread(void *arg) +{ + struct async_req *async_req = arg; + async_req->req_valid = + ls_get_request(async_req->ls, &async_req->actual); + + return NULL; +} + +static struct async_req * +get_and_check_request_async(struct ls *ls, struct lease_handle *expected_lease, + enum ls_req_type expected_type) + +{ + struct async_req *req = malloc(sizeof(struct async_req)); + ck_assert_ptr_ne(req, NULL); + + *req = (struct async_req){ + .ls = ls, + .expected = + { + .lease_handle = expected_lease, + .type = expected_type, + }, + }; + + int ret = pthread_create(&req->tid, NULL, get_request_thread, req); + ck_assert_int_eq(ret, 0); + + return req; +} + +static void check_async_req_result(struct async_req *req) +{ + + pthread_join(req->tid, NULL); + ck_assert_int_eq(req->req_valid, true); + check_request(&req->actual, req->expected.lease_handle, + req->expected.type); + free(req); +} + +/* issue_lease_request_and_release + * + * Test details: Generate a lease request and lease release command from + * a client. + * Expected results: One get lease and one release lease request are returned + * from ls_get_request(). + */ +START_TEST(issue_lease_request_and_release) +{ + struct ls *ls = create_default_server(); + + struct client_state *cstate = test_client_start(&default_test_config); + + get_and_check_request(ls, &test_lease, LS_REQ_GET_LEASE); + test_client_stop(cstate); + get_and_check_request(ls, &test_lease, LS_REQ_RELEASE_LEASE); +} +END_TEST + +/* issue_lease_request_and_early_release + * + * Test details: Close client connection immediately after connecting (before + * lease request is processed) + * Expected results: Should be the same result as + * issue_lease_request_and_release. + */ +START_TEST(issue_lease_request_and_early_release) +{ + struct ls *ls = create_default_server(); + + struct client_state *cstate = test_client_start(&default_test_config); + + test_client_stop(cstate); + get_and_check_request(ls, &test_lease, LS_REQ_GET_LEASE); + get_and_check_request(ls, &test_lease, LS_REQ_RELEASE_LEASE); +} +END_TEST + +/* issue_multiple_lease_requests + * + * Test details: Generate multiple lease requests to the same lease server from + * multiple clients at the same time + * Expected results: One get lease and one release lease request are returned + * from ls_get_request(). + * Requests from all but the first client are rejected + * (sockets are closed). + */ +START_TEST(issue_multiple_lease_requests) +{ + struct lease_handle *leases[] = { + &test_lease, + }; + struct ls *ls = ls_create(leases, 1); + + struct test_config accepted_config; + struct client_state *accepted_cstate; + + accepted_config = default_test_config; + accepted_cstate = test_client_start(&accepted_config); + get_and_check_request(ls, &test_lease, LS_REQ_GET_LEASE); + + /*Try to make additional connections while the first is still + *connected. */ + const int nextra_clients = 2; + struct test_config extra_configs[nextra_clients]; + struct client_state *extra_cstates[nextra_clients]; + + for (int i = 0; i < nextra_clients; i++) { + extra_configs[i] = default_test_config; + extra_cstates[i] = test_client_start(&extra_configs[i]); + } + + // Start asyncronously checking for the accepted client to release. + struct async_req *async_release_req = + get_and_check_request_async(ls, &test_lease, LS_REQ_RELEASE_LEASE); + + for (int i = 0; i < nextra_clients; i++) { + test_client_stop(extra_cstates[i]); + } + + /* Release the first connection and check results */ + test_client_stop(accepted_cstate); + check_async_req_result(async_release_req); + + /* Only one connection should be granted access by the lease manager */ + ck_assert_int_eq(accepted_config.connection_completed, true); + for (int i = 0; i < nextra_clients; i++) + ck_assert_int_eq(extra_configs[i].connection_completed, false); +} +END_TEST + +static void add_client_request_tests(Suite *s) +{ + TCase *tc = tcase_create("Client request testing"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, issue_lease_request_and_release); + tcase_add_test(tc, issue_lease_request_and_early_release); + tcase_add_test(tc, issue_multiple_lease_requests); + suite_add_tcase(s, tc); +} + +/************** File descriptor sending tests ************/ + +/* Test the sending (and failure to send) of file descriptors + * to the client. + */ + +/* send_fd_to_client + * + * Test details: Send a valid fd to a given client. + * Expected results: The correct fd is successfully sent. + */ +START_TEST(send_fd_to_client) +{ + struct ls *ls = create_default_server(); + + struct client_state *cstate = test_client_start(&default_test_config); + + struct ls_req req; + bool req_valid = ls_get_request(ls, &req); + ck_assert_int_eq(req_valid, true); + check_request(&req, &test_lease, LS_REQ_GET_LEASE); + + /* send an fd to the client*/ + int test_fd = get_dummy_fd(); + ck_assert_int_eq(ls_send_fd(ls, req.server, test_fd), true); + + test_client_stop(cstate); + get_and_check_request(ls, &test_lease, LS_REQ_RELEASE_LEASE); + + ck_assert_int_eq(default_test_config.connection_completed, true); + ck_assert_int_eq(default_test_config.has_data, true); + check_fd_equality(test_fd, default_test_config.received_fd); +} +END_TEST + +/* ls_send_fd_is_noop_when_fd_is_invalid + * + * Test details: Call ls_send_fd() with an invalid fd. + * Expected results: No fd is sent to client. The connection to the + * client is closed. + */ +START_TEST(ls_send_fd_is_noop_when_fd_is_invalid) +{ + struct ls *ls = create_default_server(); + + struct client_state *cstate = test_client_start(&default_test_config); + + struct ls_req req; + bool req_valid = ls_get_request(ls, &req); + ck_assert_int_eq(req_valid, true); + check_request(&req, &test_lease, LS_REQ_GET_LEASE); + + int invalid_fd = get_dummy_fd(); + close(invalid_fd); + + ck_assert_int_eq(ls_send_fd(ls, req.server, invalid_fd), false); + + test_client_stop(cstate); + get_and_check_request(ls, &test_lease, LS_REQ_RELEASE_LEASE); + ck_assert_int_eq(default_test_config.connection_completed, true); + ck_assert_int_eq(default_test_config.has_data, false); +} +END_TEST + +static void add_fd_send_tests(Suite *s) +{ + TCase *tc = tcase_create("File descriptor sending tests"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, send_fd_to_client); + tcase_add_test(tc, ls_send_fd_is_noop_when_fd_is_invalid); + suite_add_tcase(s, tc); +} + +int main(void) +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = suite_create("DLM lease server tests"); + + add_error_tests(s); + add_client_request_tests(s); + add_fd_send_tests(s); + + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/drm-lease-manager/test/meson.build b/drm-lease-manager/test/meson.build new file mode 100644 index 0000000..7d42bec --- /dev/null +++ b/drm-lease-manager/test/meson.build @@ -0,0 +1,30 @@ +check_dep = dependency('check') + +ls_inc = include_directories('..') + +ls_objects = main.extract_objects(lease_server_files) +ls_test_sources = [ + 'lease-server-test.c', + 'test-socket-client.c', +] + +ls_test = executable('lease-server-test', + sources: ls_test_sources, + objects: ls_objects, + dependencies: [check_dep, fff_dep, dlmcommon_dep, thread_dep], + include_directories: ls_inc) + +lm_objects = main.extract_objects(lease_manager_files) +lm_test_sources = [ + 'lease-manager-test.c', + 'test-drm-device.c', +] + +lm_test = executable('lease-manager-test', + sources: lm_test_sources, + objects: lm_objects, + dependencies: [check_dep, fff_dep, dlmcommon_dep, drm_dep], + include_directories: ls_inc) + +test('DRM Lease manager - socket server test', ls_test, is_parallel: false) +test('DRM Lease manager - DRM interface test', lm_test) diff --git a/drm-lease-manager/test/test-drm-device.c b/drm-lease-manager/test/test-drm-device.c new file mode 100644 index 0000000..844599a --- /dev/null +++ b/drm-lease-manager/test/test-drm-device.c @@ -0,0 +1,122 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <check.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <xf86drmMode.h> + +#include "test-drm-device.h" +#define UNUSED(x) (void)(x) + +/* Set the base value for IDs of each resource type. + * These can be adjusted if test cases need more IDs. */ +#define IDS_PER_RES_TYPE 32 + +#define CRTC_BASE (IDS_PER_RES_TYPE) +#define CONNECTOR_BASE (CRTC_BASE + IDS_PER_RES_TYPE) +#define ENCODER_BASE (CONNECTOR_BASE + IDS_PER_RES_TYPE) +#define PLANE_BASE (ENCODER_BASE + IDS_PER_RES_TYPE) +#define LESSEE_ID_BASE (PLANE_BASE + IDS_PER_RES_TYPE) + +struct drm_device test_device; + +#define ALLOC_RESOURCE(res, container) \ + do { \ + if (res != 0) { \ + test_device.container.res = \ + malloc(sizeof(uint32_t) * res); \ + if (!test_device.container.res) \ + return false; \ + test_device.container.count_##res = res; \ + } \ + } while (0) + +#define FILL_RESOURCE(res, RES, container) \ + for (int i = 0; i < res; i++) { \ + test_device.container.res[i] = RES##_BASE + i; \ + } + +bool setup_drm_test_device(int crtcs, int connectors, int encoders, int planes) +{ + int lessee_ids = crtcs; + ALLOC_RESOURCE(crtcs, resources); + ALLOC_RESOURCE(connectors, resources); + ALLOC_RESOURCE(encoders, resources); + ALLOC_RESOURCE(planes, plane_resources); + ALLOC_RESOURCE(lessee_ids, leases); + + FILL_RESOURCE(crtcs, CRTC, resources); + FILL_RESOURCE(connectors, CONNECTOR, resources); + FILL_RESOURCE(encoders, ENCODER, resources); + FILL_RESOURCE(planes, PLANE, plane_resources); + FILL_RESOURCE(lessee_ids, LESSEE_ID, leases); + + return true; +} + +void reset_drm_test_device(void) +{ + free(test_device.resources.crtcs); + free(test_device.resources.connectors); + free(test_device.resources.encoders); + free(test_device.plane_resources.planes); + free(test_device.leases.lessee_ids); + memset(&test_device, 0, sizeof(test_device)); +} + +void setup_test_device_layout(drmModeConnector *connectors, + drmModeEncoder *encoders, drmModePlane *planes) +{ + test_device.layout.connectors = connectors; + test_device.layout.encoders = encoders; + test_device.layout.planes = planes; +} + +#define GET_DRM_RESOURCE_FN(Res, res, RES, container) \ + drmMode##Res##Ptr get_##res(int fd, uint32_t id) \ + { \ + UNUSED(fd); \ + if (id == 0) \ + return NULL; \ + ck_assert_int_ge(id, RES##_BASE); \ + ck_assert_int_lt( \ + id, RES##_BASE + test_device.container.count_##res##s); \ + return &test_device.layout.res##s[id - RES##_BASE]; \ + } + +GET_DRM_RESOURCE_FN(Connector, connector, CONNECTOR, resources) +GET_DRM_RESOURCE_FN(Encoder, encoder, ENCODER, resources) +GET_DRM_RESOURCE_FN(Plane, plane, PLANE, plane_resources) + +int create_lease(int fd, const uint32_t *objects, int num_objects, int flags, + uint32_t *lessee_id) +{ + UNUSED(fd); + UNUSED(objects); + UNUSED(num_objects); + UNUSED(flags); + + int lease_count = test_device.leases.count; + if (lease_count < test_device.leases.count_lessee_ids) + *lessee_id = test_device.leases.lessee_ids[lease_count]; + else + *lessee_id = 0; + + test_device.leases.count++; + + return 0; +} diff --git a/drm-lease-manager/test/test-drm-device.h b/drm-lease-manager/test/test-drm-device.h new file mode 100644 index 0000000..1d5b683 --- /dev/null +++ b/drm-lease-manager/test/test-drm-device.h @@ -0,0 +1,81 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_DRM_DEVICE_H +#define TEST_DRM_DEVICE_H + +#include <xf86drmMode.h> + +/* TEST_DRM_DEVICE can be the path to any + * file that can be opened. + */ +#define TEST_DRM_DEVICE "/dev/null" + +struct drm_device { + drmModeRes resources; + drmModePlaneRes plane_resources; + struct { + drmModeConnector *connectors; + drmModeEncoder *encoders; + drmModePlane *planes; + } layout; + + struct { + int count; + uint32_t *lessee_ids; + int count_lessee_ids; + } leases; +}; + +extern struct drm_device test_device; + +bool setup_drm_test_device(int crtcs, int connectors, int encoders, int planes); +void setup_test_device_layout(drmModeConnector *connectors, + drmModeEncoder *encoders, drmModePlane *planes); +void reset_drm_test_device(void); + +drmModeConnectorPtr get_connector(int fd, uint32_t id); +drmModeEncoderPtr get_encoder(int fd, uint32_t id); +drmModePlanePtr get_plane(int fd, uint32_t id); +int create_lease(int fd, const uint32_t *objects, int num_objects, int flags, + uint32_t *lessee_id); + +#define TEST_DEVICE_RESOURCES (&test_device.resources) +#define TEST_DEVICE_PLANE_RESOURCES (&test_device.plane_resources) + +#define CRTC_ID(x) (test_device.resources.crtcs[x]) +#define CONNECTOR_ID(x) (test_device.resources.connectors[x]) +#define ENCODER_ID(x) (test_device.resources.encoders[x]) +#define PLANE_ID(x) (test_device.plane_resources.planes[x]) +#define LESSEE_ID(x) (test_device.leases.lessee_ids[x]) + +#define CONNECTOR(cid, eid, encs, enc_cnt) \ + { \ + .connector_id = cid, .encoder_id = eid, \ + .count_encoders = enc_cnt, .encoders = encs, \ + } + +#define ENCODER(eid, crtc, crtc_mask) \ + { \ + .encoder_id = eid, .crtc_id = crtc, \ + .possible_crtcs = crtc_mask, \ + } + +#define PLANE(pid, crtc_mask) \ + { \ + .plane_id = pid, .possible_crtcs = crtc_mask, \ + } + +#endif diff --git a/drm-lease-manager/test/test-socket-client.c b/drm-lease-manager/test/test-socket-client.c new file mode 100644 index 0000000..260437a --- /dev/null +++ b/drm-lease-manager/test/test-socket-client.c @@ -0,0 +1,162 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test-socket-client.h" + +#include <check.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "socket-path.h" + +#define DEFAULT_RECV_TIMEOUT (100) // timeout in ms to receive data from server + +struct client_state { + pthread_t tid; + + int socket_fd; + struct test_config *config; +}; + +static void client_gst_socket_status(int socket_fd, struct test_config *config) +{ + + config->connection_completed = true; + + struct pollfd pfd = {.fd = socket_fd, .events = POLLIN}; + if (poll(&pfd, 1, config->recv_timeout) <= 0) + return; + + if (pfd.revents & POLLHUP) + config->connection_completed = false; + + if (pfd.revents & POLLIN) + config->has_data = true; + + return; +} + +static int receive_fd_from_socket(int sockfd) +{ + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + char data; + struct iovec iov = {.iov_base = &data, .iov_len = sizeof(data)}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = u.buf, + .msg_controllen = sizeof(u.buf), + }; + + if (recvmsg(sockfd, &msg, 0) < 0) + return -1; + + int recv_fd = -1; + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + ck_assert_int_eq(cmsg->cmsg_level, SOL_SOCKET); + + if (cmsg->cmsg_type != SCM_RIGHTS) + continue; + + int nfds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + ck_assert_int_eq(nfds, 1); + recv_fd = *(int *)CMSG_DATA(cmsg); + } + return recv_fd; +} + +static void *test_client_thread(void *arg) +{ + struct client_state *cstate = arg; + struct test_config *config = cstate->config; + + struct sockaddr_un address = { + .sun_family = AF_UNIX, + }; + + ck_assert_int_eq( + sockaddr_set_lease_server_path(&address, config->lease->name), + true); + + int client = socket(PF_UNIX, SOCK_STREAM, 0); + ck_assert_int_ge(client, 0); + + int ret; + ret = connect(client, (struct sockaddr *)&address, sizeof(address)); + if (ret != 0) { + printf("Connect failed;: %s\n", strerror(errno)); + close(client); + return NULL; + } + + if (!config->recv_timeout) + config->recv_timeout = DEFAULT_RECV_TIMEOUT; + + client_gst_socket_status(client, config); + + if (config->has_data) { + config->received_fd = receive_fd_from_socket(client); + } + + cstate->socket_fd = client; + + return NULL; +} + +struct client_state *test_client_start(struct test_config *test_config) +{ + struct client_state *cstate = malloc(sizeof(*cstate)); + + *cstate = (struct client_state){ + .config = test_config, + }; + + pthread_create(&cstate->tid, NULL, test_client_thread, cstate); + + return cstate; +} + +void test_client_stop(struct client_state *cstate) +{ + + ck_assert_ptr_ne(cstate, NULL); + + pthread_join(cstate->tid, NULL); + + if (cstate->socket_fd >= 0) + close(cstate->socket_fd); + + free(cstate); +} + +void test_config_cleanup(struct test_config *config) +{ + if (config->has_data && config->received_fd >= 0) + close(config->received_fd); +} diff --git a/drm-lease-manager/test/test-socket-client.h b/drm-lease-manager/test/test-socket-client.h new file mode 100644 index 0000000..b5bdf6d --- /dev/null +++ b/drm-lease-manager/test/test-socket-client.h @@ -0,0 +1,37 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_SOCKET_CLIENT_H +#define TEST_SOCKET_CLIENT_H +#include <stdbool.h> + +#include "drm-lease.h" +struct test_config { + // settings + struct lease_handle *lease; + int recv_timeout; + + // outputs + int received_fd; + bool has_data; + bool connection_completed; +}; + +void test_config_cleanup(struct test_config *config); + +struct client_state; +struct client_state *test_client_start(struct test_config *test_config); +void test_client_stop(struct client_state *cstate); +#endif diff --git a/examples/dlm-client-test/dlm-client-test.c b/examples/dlm-client-test/dlm-client-test.c new file mode 100644 index 0000000..411fce7 --- /dev/null +++ b/examples/dlm-client-test/dlm-client-test.c @@ -0,0 +1,94 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dlmclient.h" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <xf86drmMode.h> + +static void usage(const char *name) +{ + fprintf(stderr, + "%s <lease name>\n" + "\tlease name: Name of lease to check\n", + name); +} + +static void dump_lease_resources(int lease_fd) +{ + drmModeObjectListPtr ol = drmModeGetLease(lease_fd); + if (!ol) { + fprintf( + stderr, + "drmModeGetLease failed. Received fd is not a DRM lease\n"); + return; + } + free(ol); + + drmModeRes *res = drmModeGetResources(lease_fd); + if (!res) { + fprintf(stderr, "drmModeGetResources failed\n"); + return; + } + + for (int i = 0; i < res->count_crtcs; i++) + printf("crtc-id: %u\n", res->crtcs[i]); + + for (int i = 0; i < res->count_connectors; i++) + printf("connector-id: %u\n", res->connectors[i]); + + drmModeFreeResources(res); + + drmModePlaneRes *plane_res = drmModeGetPlaneResources(lease_fd); + if (!plane_res) { + fprintf(stderr, "drmModeGetPlaneResources failed\n"); + return; + } + + for (uint32_t i = 0; i < plane_res->count_planes; i++) + printf("plane-id: %u\n", plane_res->planes[i]); + + drmModeFreePlaneResources(plane_res); +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + usage(argv[0]); + return EXIT_FAILURE; + } + + struct dlm_lease *lease = dlm_get_lease(argv[1]); + if (!lease) { + fprintf(stderr, "dlm_get_lease: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + int lease_fd = dlm_lease_fd(lease); + if (lease_fd < 0) { + fprintf(stderr, "dlm_lease_fd: %s\n", strerror(errno)); + dlm_release_lease(lease); + return EXIT_FAILURE; + } + + dump_lease_resources(lease_fd); + dlm_release_lease(lease); + return EXIT_SUCCESS; +} diff --git a/examples/dlm-client-test/meson.build b/examples/dlm-client-test/meson.build new file mode 100644 index 0000000..aa51c78 --- /dev/null +++ b/examples/dlm-client-test/meson.build @@ -0,0 +1,4 @@ +executable('dlm-client-test', + ['dlm-client-test.c'], + dependencies : [dlmclient_dep, drm_dep], +) diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000..706c2b8 --- /dev/null +++ b/examples/meson.build @@ -0,0 +1 @@ +subdir('dlm-client-test') diff --git a/libdlmclient/dlmclient.c b/libdlmclient/dlmclient.c new file mode 100644 index 0000000..32493d3 --- /dev/null +++ b/libdlmclient/dlmclient.c @@ -0,0 +1,170 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dlmclient.h" +#include "log.h" +#include "socket-path.h" + +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +void dlm_enable_debug_log(bool enable) +{ + dlm_log_enable_debug(enable); +} + +struct dlm_lease { + int dlm_server_sock; + int lease_fd; +}; + +static bool lease_connect(struct dlm_lease *lease, const char *name) +{ + struct sockaddr_un sa = { + .sun_family = AF_UNIX, + }; + + if (!sockaddr_set_lease_server_path(&sa, name)) + return false; + + int dlm_server_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (dlm_server_sock < 0) { + DEBUG_LOG("Socket creation failed: %s\n", strerror(errno)); + return false; + } + + while (connect(dlm_server_sock, (struct sockaddr *)&sa, + sizeof(struct sockaddr_un)) == -1) { + if (errno == EINTR) + continue; + DEBUG_LOG("Cannot connect to %s: %s\n", sa.sun_path, + strerror(errno)); + close(dlm_server_sock); + return false; + } + lease->dlm_server_sock = dlm_server_sock; + return true; +} + +static bool lease_recv_fd(struct dlm_lease *lease) +{ + char ctrl_buf[CMSG_SPACE(sizeof(int))] = {0}; + char data[1] = {0}; + + struct iovec iov[1]; + iov[0].iov_base = data; + iov[0].iov_len = sizeof(data); + + struct msghdr msg = { + .msg_control = ctrl_buf, + .msg_controllen = CMSG_SPACE(sizeof(int)), + .msg_iov = iov, + .msg_iovlen = 1, + }; + + int ret; + while ((ret = recvmsg(lease->dlm_server_sock, &msg, 0)) <= 0) { + if (ret == 0) { + errno = EACCES; + DEBUG_LOG("Request rejected by DRM lease manager\n"); + // TODO: Report why the request was rejected. + return false; + } + if (errno != EINTR) { + DEBUG_LOG("Socket data receive error: %s\n", + strerror(errno)); + return false; + } + } + + lease->lease_fd = -1; + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int nfds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + int *fds = (int *)CMSG_DATA(cmsg); + + if (nfds == 1) { + lease->lease_fd = fds[0]; + break; + } + + DEBUG_LOG( + "Expected 1 fd from lease manager. Received %d\n", + nfds); + /* Close any unexpected fds so we don't leak them. */ + for (int i = 0; i < nfds; i++) + close(fds[i]); + break; + } + } + + if (lease->lease_fd < 0) { + DEBUG_LOG("Expected data not received from lease manager\n"); + errno = EPROTO; + return false; + } + + return true; +} + +struct dlm_lease *dlm_get_lease(const char *name) +{ + struct dlm_lease *lease = calloc(1, sizeof(struct dlm_lease)); + if (!lease) { + DEBUG_LOG("can't allocate memory : %s\n", strerror(errno)); + return NULL; + } + + if (!lease_connect(lease, name)) { + free(lease); + return NULL; + } + + if (!lease_recv_fd(lease)) { + close(lease->dlm_server_sock); + free(lease); + return NULL; + } + + return lease; +} + +void dlm_release_lease(struct dlm_lease *lease) +{ + if (!lease) + return; + + close(lease->lease_fd); + close(lease->dlm_server_sock); + free(lease); +} + +int dlm_lease_fd(struct dlm_lease *lease) +{ + if (!lease) + return -1; + + return lease->lease_fd; +} diff --git a/libdlmclient/dlmclient.h b/libdlmclient/dlmclient.h new file mode 100644 index 0000000..908cd6b --- /dev/null +++ b/libdlmclient/dlmclient.h @@ -0,0 +1,86 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file dlmclient.h + */ +#ifndef DLM_CLIENT_H +#define DLM_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> + +/** + * @brief Enable debug logging + * + * @param[in] enable enable/disable debug logging + */ +void dlm_enable_debug_log(bool enable); + +/** + * @brief lease handle + */ +struct dlm_lease; + +/** + * @brief Get a DRM lease from the lease manager + * + * @param[in] name requested lease + * @return A pointer to a lease handle on success. + * On error this function returns NULL and errno is set accordingly. + * + * Possible errors: + * + * errno | Meaning + * -------------|------------------------------------------------------------- + * EACCESS | Cannot access lease manager socket directory + * EACCESS | Lease request denied by lease manager + * ENAMETOOLONG | The path to the lease manager socket directory is too long + * ENOENT | Lease manager or requested lease not available + * ENOMEM | Out of memory during operation + * EPROTO | Protocol error in communication with lease manager + * + * This list is not exhaustive, and errno may be set to other error codes, + * especially those related to socket communication. + */ +struct dlm_lease *dlm_get_lease(const char *name); + +/** + * @brief Release a lease handle + * + * @details Release a lease handle. The lease handle will be invalidated and + * the associated DRM lease wil be revoked. Any fd's retrieved from + * dlm_lease_fd() will be closed. + * @param[in] lease pointer to lease handle + */ +void dlm_release_lease(struct dlm_lease *lease); + +/** + * @brief Get a DRM Master fd from a valid lease handle + * + * @param[in] lease pointer to a lease handle + * @return A DRM Master file descriptor for the lease on success. + * -1 is returned when called with a NULL lease handle. + */ +int dlm_lease_fd(struct dlm_lease *lease); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libdlmclient/docs/Doxyfile.in b/libdlmclient/docs/Doxyfile.in new file mode 100644 index 0000000..dce4244 --- /dev/null +++ b/libdlmclient/docs/Doxyfile.in @@ -0,0 +1,9 @@ +# General information +PROJECT_NAME = "DRM Lease Manager Client Library" +OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT@ +INPUT = @README@ @CLIENT_HEADER_DIR@ +USE_MDFILE_AS_MAINPAGE = @README@ +FILE_PATTERNS = *.h +GENERATE_LATEX = NO +OPTIMIZE_OUTPUT_FOR_C = YES +STRIP_FROM_PATH = @CLIENT_HEADER_DIR@ diff --git a/libdlmclient/docs/meson.build b/libdlmclient/docs/meson.build new file mode 100644 index 0000000..563e5fb --- /dev/null +++ b/libdlmclient/docs/meson.build @@ -0,0 +1,21 @@ +doxygen = find_program('doxygen', required : false) + +readme = join_paths(meson.source_root(), 'README.md') + +if get_option('enable-docs') and doxygen.found() + conf_data = configuration_data() + conf_data.set('README', readme) + conf_data.set('CLIENT_HEADER_DIR', dlmclient_header_dir) + conf_data.set('DOXYGEN_OUTPUT', meson.current_build_dir()) + doxyfile = configure_file( + input: 'Doxyfile.in', + output: 'Doxyfile', + configuration: conf_data + ) + custom_target('docs', + input: [doxyfile, readme, dlmclient_headers], + build_by_default: true, + command: [doxygen, '@INPUT0@'], + output: ['html'] + ) +endif diff --git a/libdlmclient/meson.build b/libdlmclient/meson.build new file mode 100644 index 0000000..d63a842 --- /dev/null +++ b/libdlmclient/meson.build @@ -0,0 +1,38 @@ +dlmclient_sources = files( + 'dlmclient.c' +) + +dlmclient_headers = files( + 'dlmclient.h' +) + +libdlmclient = library( + 'dlmclient', + sources: dlmclient_sources, + version: meson.project_version(), + dependencies: [dlmcommon_dep], + install: true, +) + +dlmclient_dep = declare_dependency( + link_with: libdlmclient, + include_directories: include_directories('.') +) + + +install_headers(dlmclient_headers, subdir: 'libdlmclient') + +pkg.generate( + name: 'libdlmclient', + libraries: libdlmclient, + subdirs: [ 'libdlmclient' ], + version: meson.project_version(), + description: 'DRM lease manager client library', +) + +dlmclient_header_dir = meson.current_source_dir() +subdir('docs') + +if enable_tests + subdir('test') +endif diff --git a/libdlmclient/test/libdlmclient-test.c b/libdlmclient/test/libdlmclient-test.c new file mode 100644 index 0000000..5b04c09 --- /dev/null +++ b/libdlmclient/test/libdlmclient-test.c @@ -0,0 +1,291 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <check.h> + +#include <dirent.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "config.h" +#include "dlmclient.h" +#include "test-helpers.h" +#include "test-socket-server.h" + +#define SOCKETDIR "/tmp" + +#define TEST_LEASE_NAME "test-lease" + +/************** Test fixutre functions *************/ +struct test_config default_test_config; + +static void test_setup(void) +{ + dlm_enable_debug_log(true); + setenv("DLM_RUNTIME_PATH", SOCKETDIR, 1); + + default_test_config = (struct test_config){ + .lease_name = TEST_LEASE_NAME, + .nfds = 1, + }; +} + +static void test_shutdown(void) +{ + test_config_cleanup(&default_test_config); +} + +/************** Lease manager error tests *************/ + +/* These tests verify that the client library gracefully handles + * failures when trying to receive data from the lease manager. + * Failures or errors in the lease manager should cause meaningful + * errors to be reported by the client. Lease manager errors should + * not cause crashes or invalid state in the client */ + +/* manager_connection_err + * + * Test details: Simulate socket connection failure. + * Expected results: dlm_get_lease() fails. + */ +START_TEST(manager_connection_err) +{ + struct server_state *sstate = test_server_start(&default_test_config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME "-bad"); + + ck_assert_ptr_eq(lease, NULL); + + test_server_stop(sstate); +} +END_TEST + +/* no_data_from_manager + * + * Test details: Close the remote (lease manager) without sending any data. + * Currently this means that the lease request has been rejected + * for some reason. + * + * TODO: Update this when the client-server protocol is updated to + * include the reason for the lease rejection. + * + * Expected results: dlm_get_lease() fails, errno set to EACCESS. + */ +START_TEST(no_data_from_manager) +{ + + struct test_config config = { + .lease_name = TEST_LEASE_NAME, + .send_no_data = true, + }; + + struct server_state *sstate = test_server_start(&config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + + ck_assert_ptr_eq(lease, NULL); + ck_assert_int_eq(errno, EACCES); + + test_server_stop(sstate); +} +END_TEST + +/* no_lease_fd_from_manager + * + * Test details: Simulate receiving response from lease manager with + * no fd attached. (i.e. a protocol error) + * + * Expected results: dlm_get_lease() fails, errno set to EPROTO. + */ +START_TEST(no_lease_fd_from_manager) +{ + /* Receive message from the lease manager with missing lease fd */ + struct test_config config = { + .lease_name = TEST_LEASE_NAME, + .send_data_without_fd = true, + }; + + struct server_state *sstate = test_server_start(&config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + + ck_assert_ptr_eq(lease, NULL); + ck_assert_int_eq(errno, EPROTO); + + test_server_stop(sstate); +} +END_TEST + +static void add_lease_manager_error_tests(Suite *s) +{ + TCase *tc = tcase_create("Lease manager error handling"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, manager_connection_err); + tcase_add_test(tc, no_data_from_manager); + tcase_add_test(tc, no_lease_fd_from_manager); + + suite_add_tcase(s, tc); +} + +/************** Lease handling tests *****************/ + +/* These tests verify that the client library handles the received + * lease data properly. Receiving the lease fds without leaks, + * properly passing the fds to the client application and cleaning + * them up on release. + */ + +static int count_open_fds(void) +{ + int fds = 0; + DIR *dirp = opendir("/proc/self/fd"); + while ((readdir(dirp) != NULL)) + fds++; + closedir(dirp); + return fds; +} + +/* receive_fd_from_manager + * + * Test details: Successfully receive a file descriptor. + * Expected results: dlm_get_lease() succeeds. + * dlm_lease_fd() returns the correct fd value. + */ +START_TEST(receive_fd_from_manager) +{ + struct server_state *sstate = test_server_start(&default_test_config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + ck_assert_ptr_ne(lease, NULL); + + int received_fd = dlm_lease_fd(lease); + + int sent_fd = default_test_config.fds[0]; + + check_fd_equality(received_fd, sent_fd); + + dlm_release_lease(lease); + + test_server_stop(sstate); + close(sent_fd); +} +END_TEST + +/* lease_fd_is_closed_on_release + * + * Test details: Verify that dlm_release_lease() closes the lease fd. + * Expected results: lease fd is closed. + */ +START_TEST(lease_fd_is_closed_on_release) +{ + struct server_state *sstate = test_server_start(&default_test_config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + ck_assert_ptr_ne(lease, NULL); + + int received_fd = dlm_lease_fd(lease); + + check_fd_is_open(received_fd); + dlm_release_lease(lease); + check_fd_is_closed(received_fd); + + test_server_stop(sstate); +} +END_TEST + +/* dlm_lease_fd_always_returns_same_lease + * + * Test details: Verify that dlm_lease_fd() always returns the same value + * for a given lease. + * Expected results: same value is returned when called multiple times. + */ +START_TEST(dlm_lease_fd_always_returns_same_lease) +{ + struct server_state *sstate = test_server_start(&default_test_config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + ck_assert_ptr_ne(lease, NULL); + + int received_fd = dlm_lease_fd(lease); + + ck_assert_int_eq(received_fd, dlm_lease_fd(lease)); + ck_assert_int_eq(received_fd, dlm_lease_fd(lease)); + + dlm_release_lease(lease); + + test_server_stop(sstate); +} +END_TEST + +START_TEST(verify_that_unused_fds_are_not_leaked) +{ + int nopen_fds = count_open_fds(); + + struct test_config config = { + .lease_name = TEST_LEASE_NAME, + .nfds = 2, + }; + + struct server_state *sstate = test_server_start(&config); + + struct dlm_lease *lease = dlm_get_lease(TEST_LEASE_NAME); + + ck_assert_ptr_eq(lease, NULL); + ck_assert_int_eq(errno, EPROTO); + + dlm_release_lease(lease); + + test_server_stop(sstate); + + test_config_cleanup(&config); + ck_assert_int_eq(nopen_fds, count_open_fds()); +} +END_TEST + +static void add_lease_handling_tests(Suite *s) +{ + TCase *tc = tcase_create("Lease processing tests"); + + tcase_add_checked_fixture(tc, test_setup, test_shutdown); + + tcase_add_test(tc, receive_fd_from_manager); + tcase_add_test(tc, lease_fd_is_closed_on_release); + tcase_add_test(tc, dlm_lease_fd_always_returns_same_lease); + tcase_add_test(tc, verify_that_unused_fds_are_not_leaked); + suite_add_tcase(s, tc); +} + +int main(void) +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = suite_create("DLM client library tests"); + + add_lease_manager_error_tests(s); + add_lease_handling_tests(s); + + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/libdlmclient/test/meson.build b/libdlmclient/test/meson.build new file mode 100644 index 0000000..35e8575 --- /dev/null +++ b/libdlmclient/test/meson.build @@ -0,0 +1,6 @@ +cl_test = executable('libdlmclient-test', + sources: [ 'libdlmclient-test.c', 'test-socket-server.c'], + dependencies: [check_dep, fff_dep, dlmcommon_dep, dlmclient_dep, thread_dep], + include_directories: configuration_inc) + +test('Client library test', cl_test, is_parallel: false) diff --git a/libdlmclient/test/test-socket-server.c b/libdlmclient/test/test-socket-server.c new file mode 100644 index 0000000..281aaf7 --- /dev/null +++ b/libdlmclient/test/test-socket-server.c @@ -0,0 +1,169 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test-socket-server.h" +#include <check.h> + +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "socket-path.h" +#include "test-helpers.h" + +static void send_fd_list_over_socket(int socket, int nfds, int *fds) +{ + char data; + struct iovec iov = { + .iov_base = &data, + .iov_len = 1, + }; + + int bufsize = CMSG_SPACE(nfds * sizeof(int)); + char *buf = malloc(bufsize); + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_controllen = bufsize, + .msg_control = buf, + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(nfds * sizeof(int)); + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * nfds); + + ck_assert_int_gt(sendmsg(socket, &msg, 0), 0); + free(buf); +} + +struct server_state { + pthread_t tid; + pthread_mutex_t lock; + pthread_cond_t cond; + bool is_server_started; + + struct test_config *config; +}; + +static void *test_server_thread(void *arg) +{ + struct server_state *sstate = arg; + struct test_config *config = sstate->config; + + struct sockaddr_un address = { + .sun_family = AF_UNIX, + }; + + ck_assert_int_eq( + sockaddr_set_lease_server_path(&address, config->lease_name), true); + + int server = socket(PF_UNIX, SOCK_STREAM, 0); + ck_assert_int_ge(server, 0); + + unlink(address.sun_path); + + int ret; + ret = bind(server, (struct sockaddr *)&address, sizeof(address)); + ck_assert_int_eq(ret, 0); + + ret = listen(server, 1); + ck_assert_int_eq(ret, 0); + + sstate->is_server_started = true; + pthread_cond_signal(&sstate->cond); + + int client = accept(server, NULL, NULL); + /* accept is the cancellation point for this thread. If + * pthread_cancel() is called on this thread, accept() may return + * -1, so don't assert on it. */ + + if (client < 0) { + close(server); + return NULL; + } + + if (config->send_no_data) + goto done; + + if (config->send_data_without_fd) { + char data; + write(client, &data, 1); + goto done; + } + + if (config->nfds == 0) + config->nfds = 1; + + config->fds = calloc(config->nfds, sizeof(int)); + + for (int i = 0; i < config->nfds; i++) + config->fds[i] = get_dummy_fd(); + + send_fd_list_over_socket(client, config->nfds, config->fds); +done: + close(client); + close(server); + return NULL; +} + +struct server_state *test_server_start(struct test_config *test_config) +{ + struct server_state *sstate = malloc(sizeof(*sstate)); + + *sstate = (struct server_state){ + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .is_server_started = false, + .config = test_config, + }; + + pthread_create(&sstate->tid, NULL, test_server_thread, sstate); + + pthread_mutex_lock(&sstate->lock); + while (!sstate->is_server_started) + pthread_cond_wait(&sstate->cond, &sstate->lock); + pthread_mutex_unlock(&sstate->lock); + return sstate; +} + +void test_server_stop(struct server_state *sstate) +{ + + ck_assert_ptr_ne(sstate, NULL); + + pthread_cancel(sstate->tid); + pthread_join(sstate->tid, NULL); + + free(sstate); +} + +void test_config_cleanup(struct test_config *config) +{ + if (!config->fds) + return; + + for (int i = 0; i < config->nfds; i++) + close(config->fds[i]); + + free(config->fds); +} diff --git a/libdlmclient/test/test-socket-server.h b/libdlmclient/test/test-socket-server.h new file mode 100644 index 0000000..ebdbd2d --- /dev/null +++ b/libdlmclient/test/test-socket-server.h @@ -0,0 +1,33 @@ +/* Copyright 2020-2021 IGEL Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_SOCKET_SERVER_H +#define TEST_SOCKET_SERVER_H +#include <stdbool.h> + +struct test_config { + char *lease_name; + int nfds; + int *fds; + + bool send_data_without_fd; + bool send_no_data; +}; + +void test_config_cleanup(struct test_config *config); + +struct server_state *test_server_start(struct test_config *test_config); +void test_server_stop(struct server_state *sstate); +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..2f6cd15 --- /dev/null +++ b/meson.build @@ -0,0 +1,61 @@ +project( + 'lease_manager', 'c', + # Version based on Semantic Versioning 2.0.0 (https://semver.org/) + version: '0.1.0', + default_options: [ + 'warning_level=2', + ] +) + +config = configuration_data() + +pkg = import('pkgconfig') + +compiler_flags = [ + '-Wstrict-prototypes', + '-Wmissing-prototypes', +] + +#Setup and create runtime path +runtime_path = join_paths(get_option('localstatedir'), get_option('runtime_subdir')) +config.set_quoted('DLM_DEFAULT_RUNTIME_PATH', runtime_path) + +meson.add_install_script('sh', '-c', 'install -d $DESTDIR/$1', '_', runtime_path) + +cc = meson.get_compiler('c') +add_project_arguments( + cc.get_supported_arguments(compiler_flags), + language: 'c' +) + +configure_file(output: 'config.h', + configuration: config) + +configuration_inc = include_directories('.') + +drm_dep = dependency('libdrm', version: '>= 2.4.89') + +enable_tests = get_option('enable-tests') + +if enable_tests + check_dep = dependency('check') + thread_dep = dependency('threads') + +# Default to the system provided version of fff.h. +# otherwise fetch it ourselves. + if cc.check_header('fff.h') + fff_dep = declare_dependency() + else + if meson.version().version_compare('>=0.55') + fff_proj = subproject('fff') + fff_dep = fff_proj.get_variable('fff_dep') + else + error('Update meson version to >=0.55 to enable unit testing') + endif + endif +endif + +subdir('common') +subdir('libdlmclient') +subdir('drm-lease-manager') +subdir('examples') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..ee1f8dc --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,17 @@ +option('enable-docs', + type: 'boolean', + value: true, + description: 'Build Doxygen document if available.' +) + +option('enable-tests', + type: 'boolean', + value: false, + description: 'Build unit tests' +) + +option('runtime_subdir', + type: 'string', + value: 'run/drm-lease-manager', + description: 'subdirectory to use for runtime data' +) diff --git a/subprojects/fff.wrap b/subprojects/fff.wrap new file mode 100644 index 0000000..047a44f --- /dev/null +++ b/subprojects/fff.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = fff-1.0 + +source_url = https://github.com/meekrosoft/fff/archive/v1.0.tar.gz +source_filename = v1.0.tar.gz +source_hash = 3fbe25ac451d3f56b5b0f82b9a4dbf68e4412b3cee278f7b2feda47fc4d692c6 + +patch_directory = fff-1.0 diff --git a/subprojects/packagefiles/fff-1.0/meson.build b/subprojects/packagefiles/fff-1.0/meson.build new file mode 100644 index 0000000..c7ce1f2 --- /dev/null +++ b/subprojects/packagefiles/fff-1.0/meson.build @@ -0,0 +1,8 @@ +project('fff', 'c', + version: '1.0', + license: 'MIT' +) + +fff_dep = declare_dependency( + include_directories: include_directories('.') +) |