/* See LICENSE file for copyright and license details. */
#define IN_LIBGAMMA_LINUX_DRM
#include "common.h"
/**
* Figure out why `open` failed for a graphics card
*
* @param pathname The pathname of the error card
* @return The error code to report
*/
static int
figure_out_card_open_error(const char *pathname)
{
char buf[1024]; /* My output of `sysconf(_SC_GETGR_R_SIZE_MAX)`. */
gid_t supplemental_groups[NGROUPS_MAX];
struct group *group, _grp;
struct stat attr;
int i, n;
/* Check which the device exists */
if (errno == ENXIO || errno == ENODEV)
return LIBGAMMA_NO_SUCH_PARTITION;
/* If we did not get access permission, figure out why */
if (errno != EACCES) {
/* If we could not figure out what went
* wrong, just return the error we got */
return LIBGAMMA_ERRNO_SET;
}
/* TODO Can this be simplified? Is this even correct? */
#define TEST(R, W) ((attr.st_mode & ((R) | (W))) == ((R) | (W)))
/* Get permission requirement for the file */
if (stat(pathname, &attr))
return errno == EACCES ? LIBGAMMA_NO_SUCH_PARTITION : LIBGAMMA_ERRNO_SET;
/* Test owner's, group's and others' permissions */
if ((attr.st_uid == geteuid() && TEST(S_IRUSR, S_IWUSR)) ||
(attr.st_gid == getegid() && TEST(S_IRGRP, S_IWGRP)) || TEST(S_IROTH, S_IWOTH))
return LIBGAMMA_DEVICE_ACCESS_FAILED;
/* The group should be "video", but perhaps
* it is "root" to restrict users */
if (!attr.st_gid /* root group */ || TEST(S_IRGRP, S_IWGRP))
return LIBGAMMA_DEVICE_RESTRICTED;
/* Get the user's supplemental group membership list */
n = getgroups(NGROUPS_MAX, supplemental_groups);
if (n < 0)
return LIBGAMMA_ERRNO_SET;
/* Test whether any of the supplemental
* group should be satisfactory */
for (i = 0; i < n; i++)
if (supplemental_groups[i] == attr.st_gid)
break;
/* If one of the supplemental groups should be satisfactory,
* then we do not know anything more than that access failed */
if (i != n)
return LIBGAMMA_DEVICE_ACCESS_FAILED;
/* Otherwise, try to get the name of the group that is
* required and report the missing group membership */
errno = getgrgid_r(attr.st_gid, &_grp, buf, sizeof(buf), &group);
if (errno == ERANGE) {
/* The lenght of the group's name is absurdly long, degrade to thread-unsafe. */
errno = 0;
group = getgrgid(attr.st_gid);
} else if (errno) {
return LIBGAMMA_ERRNO_SET;
}
libgamma_group_gid_set(attr.st_gid);
libgamma_group_name_set(group ? group->gr_name : NULL);
return LIBGAMMA_DEVICE_REQUIRE_GROUP;
#undef TEST
}
/**
* Initialise an allocated partition state
*
* @param this The partition state to initialise
* @param site The site state for the site that the partition belongs to
* @param partition The the index of the partition within the site
* @return Zero on success, otherwise (negative) the value of an
* error identifier provided by this library
*/
int
libgamma_linux_drm_partition_initialise(struct libgamma_partition_state *restrict this,
struct libgamma_site_state *restrict site, size_t partition)
{
int rc = 0;
struct libgamma_drm_card_data *restrict data;
char pathname[PATH_MAX];
(void) site;
/* Check for partition index overflow */
if (partition > INT_MAX)
return LIBGAMMA_NO_SUCH_PARTITION;
/* Allocate and initialise graphics card data */
this->data = NULL;
data = malloc(sizeof(*data));
if (!data)
return LIBGAMMA_ERRNO_SET;
data->fd = -1;
data->res = NULL;
data->encoders = NULL;
data->connectors = NULL;
/* Get the pathname for the graphics card */
snprintf(pathname, sizeof(pathname), DRM_DEV_NAME, DRM_DIR_NAME, (int)partition);
/* Acquire access to the graphics card */
data->fd = open(pathname, O_RDWR | O_CLOEXEC);
if (data->fd < 0) {
rc = figure_out_card_open_error(pathname);
goto fail_data;
}
/* Acquire mode resources */
data->res = drmModeGetResources(data->fd);
if (!data->res) {
rc = LIBGAMMA_ACQUIRING_MODE_RESOURCES_FAILED;
goto fail_fd;
}
/* Get the number of CRTC:s that are available in the partition */
if (data->res->count_crtcs < 0) {
rc = LIBGAMMA_NEGATIVE_CRTC_COUNT;
goto fail_res;
}
this->crtcs_available = (size_t)data->res->count_crtcs;
this->data = data;
return 0;
fail_res:
drmModeFreeResources(data->res);
fail_fd:
close(data->fd);
fail_data:
free(data);
return rc;
}