Automatically detect camera capable v4l2 device.
[apps/camera-gstreamer.git] / app / utils.cpp
1 #include <sys/mman.h>
2 #include <sys/types.h>
3 #include <sys/ioctl.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <dirent.h>
7 #include <assert.h>
8 #include <cstdlib>
9 #include <cstring>
10 #include <cerrno>
11 #include <cstdio>
12 #include <cctype>
13 #include <linux/videodev2.h>
14
15 #include "utils.h"
16
17 static int
18 os_fd_set_cloexec(int fd)
19 {
20         long flags;
21
22         if (fd == -1)
23                 return -1;
24
25         flags = fcntl(fd, F_GETFD);
26         if (flags == -1)
27                 return -1;
28
29         if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
30                 return -1;
31
32         return 0;
33 }
34
35 static int
36 set_cloexec_or_close(int fd)
37 {
38         if (os_fd_set_cloexec(fd) != 0) {
39                 close(fd);
40                 return -1;
41         }
42         return fd;
43 }
44
45 static int
46 create_tmpfile_cloexec(char *tmpname)
47 {
48         int fd;
49
50 #ifdef HAVE_MKOSTEMP
51         fd = mkostemp(tmpname, O_CLOEXEC);
52         if (fd >= 0)
53                 unlink(tmpname);
54 #else
55         fd = mkstemp(tmpname);
56         if (fd >= 0) {
57                 fd = set_cloexec_or_close(fd);
58                 unlink(tmpname);
59         }
60 #endif
61
62         return fd;
63 }
64
65 /*
66  * Create a new, unique, anonymous file of the given size, and
67  * return the file descriptor for it. The file descriptor is set
68  * CLOEXEC. The file is immediately suitable for mmap()'ing
69  * the given size at offset zero.
70  *
71  * The file should not have a permanent backing store like a disk,
72  * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
73  *
74  * The file name is deleted from the file system.
75  *
76  * The file is suitable for buffer sharing between processes by
77  * transmitting the file descriptor over Unix sockets using the
78  * SCM_RIGHTS methods.
79  *
80  * If the C library implements posix_fallocate(), it is used to
81  * guarantee that disk space is available for the file at the
82  * given size. If disk space is insufficient, errno is set to ENOSPC.
83  * If posix_fallocate() is not supported, program may receive
84  * SIGBUS on accessing mmap()'ed file contents instead.
85  *
86  * If the C library implements memfd_create(), it is used to create the
87  * file purely in memory, without any backing file name on the file
88  * system, and then sealing off the possibility of shrinking it.  This
89  * can then be checked before accessing mmap()'ed file contents, to
90  * make sure SIGBUS can't happen.  It also avoids requiring
91  * XDG_RUNTIME_DIR.
92  */
93 int
94 os_create_anonymous_file(off_t size)
95 {
96         static const char weston_template[] = "/weston-shared-XXXXXX";
97         const char *path;
98         char *name;
99         int fd;
100         int ret;
101
102 #ifdef HAVE_MEMFD_CREATE
103         fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
104         if (fd >= 0) {
105                 /* We can add this seal before calling posix_fallocate(), as
106                  * the file is currently zero-sized anyway.
107                  *
108                  * There is also no need to check for the return value, we
109                  * couldn't do anything with it anyway.
110                  */
111                 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
112         } else
113 #endif
114         {
115                 path = getenv("XDG_RUNTIME_DIR");
116                 if (!path) {
117                         errno = ENOENT;
118                         return -1;
119                 }
120
121                 name = static_cast<char *>(malloc(strlen(path) + sizeof(weston_template)));
122                 if (!name)
123                         return -1;
124
125                 strcpy(name, path);
126                 strcat(name, weston_template);
127
128                 fd = create_tmpfile_cloexec(name);
129
130                 free(name);
131
132                 if (fd < 0)
133                         return -1;
134         }
135
136 #ifdef HAVE_POSIX_FALLOCATE
137         do {
138                 ret = posix_fallocate(fd, 0, size);
139         } while (ret == EINTR);
140         if (ret != 0) {
141                 close(fd);
142                 errno = ret;
143                 return -1;
144         }
145 #else
146         do {
147                 ret = ftruncate(fd, size);
148         } while (ret < 0 && errno == EINTR);
149         if (ret < 0) {
150                 close(fd);
151                 return -1;
152         }
153 #endif
154
155         return fd;
156 }
157
158 const char*
159 get_camera_device(void)
160 {
161         DIR *dir = opendir("/dev");
162         if (!dir) {
163                 perror("Couldn't open the '/dev' directory");
164                 return NULL;
165         }
166
167         static char device[PATH_MAX];
168         bool found = false;
169         while (struct dirent *dirent = readdir(dir)) {
170                 if (strncmp(dirent->d_name, "video", strlen("video")))
171                         continue;
172                 if (!isdigit(dirent->d_name[strlen("video")]))
173                         continue;
174
175                 strcpy(device, "/dev/");
176                 strncat(device, dirent->d_name, sizeof(device) - 1);
177
178                 int fd = open(device, O_RDWR);
179                 if (fd == -1)
180                         continue;
181                 struct v4l2_capability vid_cap;
182                 if (ioctl(fd, VIDIOC_QUERYCAP, &vid_cap) < 0) {
183                         close(fd);
184                         continue;
185                 }
186                 close(fd);
187
188                 if ((vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
189                         (vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
190                         found = true;
191                         break;
192                 }
193         }
194
195         closedir(dir);
196         return found ? device : NULL;
197 }