Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/lib/libcuse/cuse_lib.c
34820 views
1
/*-
2
* Copyright (c) 2010-2022 Hans Petter Selasky. All rights reserved.
3
*
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
6
* are met:
7
* 1. Redistributions of source code must retain the above copyright
8
* notice, this list of conditions and the following disclaimer.
9
* 2. Redistributions in binary form must reproduce the above copyright
10
* notice, this list of conditions and the following disclaimer in the
11
* documentation and/or other materials provided with the distribution.
12
*
13
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23
* SUCH DAMAGE.
24
*/
25
26
#include <stdio.h>
27
#include <stdint.h>
28
#include <pthread.h>
29
#include <signal.h>
30
#include <unistd.h>
31
#include <string.h>
32
#include <errno.h>
33
#include <stdlib.h>
34
#include <stdarg.h>
35
36
#include <sys/types.h>
37
#include <sys/queue.h>
38
#include <sys/fcntl.h>
39
#include <sys/mman.h>
40
#include <sys/param.h>
41
42
#include <fs/cuse/cuse_ioctl.h>
43
44
#include "cuse.h"
45
46
int cuse_debug_level;
47
48
#ifdef HAVE_DEBUG
49
static const char *cuse_cmd_str(int cmd);
50
51
#define DPRINTF(...) do { \
52
if (cuse_debug_level != 0) \
53
printf(__VA_ARGS__); \
54
} while (0)
55
#else
56
#define DPRINTF(...) do { } while (0)
57
#endif
58
59
struct cuse_vm_allocation {
60
uint8_t *ptr;
61
uint32_t size;
62
};
63
64
struct cuse_dev_entered {
65
TAILQ_ENTRY(cuse_dev_entered) entry;
66
pthread_t thread;
67
void *per_file_handle;
68
struct cuse_dev *cdev;
69
int cmd;
70
int is_local;
71
int got_signal;
72
};
73
74
struct cuse_dev {
75
TAILQ_ENTRY(cuse_dev) entry;
76
const struct cuse_methods *mtod;
77
void *priv0;
78
void *priv1;
79
};
80
81
static int f_cuse = -1;
82
83
static pthread_mutex_t m_cuse;
84
static TAILQ_HEAD(, cuse_dev) h_cuse __guarded_by(m_cuse);
85
static TAILQ_HEAD(, cuse_dev_entered) h_cuse_entered __guarded_by(m_cuse);
86
static struct cuse_vm_allocation a_cuse[CUSE_ALLOC_UNIT_MAX]
87
__guarded_by(m_cuse);
88
89
#define CUSE_LOCK() \
90
pthread_mutex_lock(&m_cuse)
91
92
#define CUSE_UNLOCK() \
93
pthread_mutex_unlock(&m_cuse)
94
95
int
96
cuse_init(void)
97
{
98
pthread_mutexattr_t attr;
99
100
f_cuse = open("/dev/cuse", O_RDWR);
101
if (f_cuse < 0) {
102
if (feature_present("cuse") == 0)
103
return (CUSE_ERR_NOT_LOADED);
104
else
105
return (CUSE_ERR_INVALID);
106
}
107
pthread_mutexattr_init(&attr);
108
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
109
pthread_mutex_init(&m_cuse, &attr);
110
111
TAILQ_INIT(&h_cuse);
112
TAILQ_INIT(&h_cuse_entered);
113
114
return (0);
115
}
116
117
int
118
cuse_uninit(void)
119
{
120
int f;
121
122
if (f_cuse < 0)
123
return (CUSE_ERR_INVALID);
124
125
f = f_cuse;
126
f_cuse = -1;
127
128
close(f);
129
130
pthread_mutex_destroy(&m_cuse);
131
132
memset(a_cuse, 0, sizeof(a_cuse));
133
134
return (0);
135
}
136
137
unsigned long
138
cuse_vmoffset(void *_ptr)
139
{
140
uint8_t *ptr_min;
141
uint8_t *ptr_max;
142
uint8_t *ptr = _ptr;
143
unsigned long remainder;
144
unsigned long n;
145
146
CUSE_LOCK();
147
for (n = remainder = 0; n != CUSE_ALLOC_UNIT_MAX; n++) {
148
if (a_cuse[n].ptr == NULL)
149
continue;
150
151
ptr_min = a_cuse[n].ptr;
152
ptr_max = a_cuse[n].ptr + a_cuse[n].size - 1;
153
154
if ((ptr >= ptr_min) && (ptr <= ptr_max)) {
155
remainder = (ptr - ptr_min);
156
break;
157
}
158
}
159
CUSE_UNLOCK();
160
161
return ((n << CUSE_ALLOC_UNIT_SHIFT) + remainder);
162
}
163
164
void *
165
cuse_vmalloc(unsigned size)
166
{
167
struct cuse_alloc_info info;
168
unsigned long pgsize;
169
unsigned long x;
170
unsigned long m;
171
unsigned long n;
172
void *ptr;
173
int error;
174
175
/* some sanity checks */
176
if (f_cuse < 0 || size < 1 || size > CUSE_ALLOC_BYTES_MAX)
177
return (NULL);
178
179
memset(&info, 0, sizeof(info));
180
181
pgsize = getpagesize();
182
info.page_count = howmany(size, pgsize);
183
184
/* compute how many units the allocation needs */
185
m = howmany(size, 1 << CUSE_ALLOC_UNIT_SHIFT);
186
if (m == 0 || m > CUSE_ALLOC_UNIT_MAX)
187
return (NULL);
188
189
CUSE_LOCK();
190
for (n = 0; n <= CUSE_ALLOC_UNIT_MAX - m; ) {
191
if (a_cuse[n].size != 0) {
192
/* skip to next available unit, depending on allocation size */
193
n += howmany(a_cuse[n].size, 1 << CUSE_ALLOC_UNIT_SHIFT);
194
continue;
195
}
196
/* check if there are "m" free units ahead */
197
for (x = 1; x != m; x++) {
198
if (a_cuse[n + x].size != 0)
199
break;
200
}
201
if (x != m) {
202
/* skip to next available unit, if any */
203
n += x + 1;
204
continue;
205
}
206
/* reserve this unit by setting the size to a non-zero value */
207
a_cuse[n].size = size;
208
CUSE_UNLOCK();
209
210
info.alloc_nr = n;
211
212
error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_MEMORY, &info);
213
214
if (error == 0) {
215
ptr = mmap(NULL, info.page_count * pgsize,
216
PROT_READ | PROT_WRITE,
217
MAP_SHARED, f_cuse, n << CUSE_ALLOC_UNIT_SHIFT);
218
219
if (ptr != MAP_FAILED) {
220
CUSE_LOCK();
221
a_cuse[n].ptr = ptr;
222
CUSE_UNLOCK();
223
224
return (ptr); /* success */
225
}
226
227
(void) ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info);
228
}
229
230
CUSE_LOCK();
231
a_cuse[n].size = 0;
232
n++;
233
}
234
CUSE_UNLOCK();
235
return (NULL); /* failure */
236
}
237
238
int
239
cuse_is_vmalloc_addr(void *ptr)
240
{
241
int n;
242
243
if (f_cuse < 0 || ptr == NULL)
244
return (0); /* false */
245
246
CUSE_LOCK();
247
for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) {
248
if (a_cuse[n].ptr == ptr)
249
break;
250
}
251
CUSE_UNLOCK();
252
253
return (n != CUSE_ALLOC_UNIT_MAX);
254
}
255
256
void
257
cuse_vmfree(void *ptr)
258
{
259
struct cuse_vm_allocation temp;
260
struct cuse_alloc_info info;
261
int error;
262
int n;
263
264
if (f_cuse < 0 || ptr == NULL)
265
return;
266
267
CUSE_LOCK();
268
for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) {
269
if (a_cuse[n].ptr != ptr)
270
continue;
271
272
temp = a_cuse[n];
273
274
CUSE_UNLOCK();
275
276
munmap(temp.ptr, temp.size);
277
278
memset(&info, 0, sizeof(info));
279
280
info.alloc_nr = n;
281
282
error = ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info);
283
284
if (error != 0) {
285
/* ignore any errors */
286
DPRINTF("Freeing memory failed: %d\n", errno);
287
}
288
CUSE_LOCK();
289
290
a_cuse[n].ptr = NULL;
291
a_cuse[n].size = 0;
292
293
break;
294
}
295
CUSE_UNLOCK();
296
}
297
298
int
299
cuse_alloc_unit_number_by_id(int *pnum, int id)
300
{
301
int error;
302
303
if (f_cuse < 0)
304
return (CUSE_ERR_INVALID);
305
306
*pnum = (id & CUSE_ID_MASK);
307
308
error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_UNIT_BY_ID, pnum);
309
if (error)
310
return (CUSE_ERR_NO_MEMORY);
311
312
return (0);
313
314
}
315
316
int
317
cuse_free_unit_number_by_id(int num, int id)
318
{
319
int error;
320
321
if (f_cuse < 0)
322
return (CUSE_ERR_INVALID);
323
324
if (num != -1 || id != -1)
325
num = (id & CUSE_ID_MASK) | (num & 0xFF);
326
327
error = ioctl(f_cuse, CUSE_IOCTL_FREE_UNIT_BY_ID, &num);
328
if (error)
329
return (CUSE_ERR_NO_MEMORY);
330
331
return (0);
332
}
333
334
int
335
cuse_alloc_unit_number(int *pnum)
336
{
337
int error;
338
339
if (f_cuse < 0)
340
return (CUSE_ERR_INVALID);
341
342
error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_UNIT, pnum);
343
if (error)
344
return (CUSE_ERR_NO_MEMORY);
345
346
return (0);
347
}
348
349
int
350
cuse_free_unit_number(int num)
351
{
352
int error;
353
354
if (f_cuse < 0)
355
return (CUSE_ERR_INVALID);
356
357
error = ioctl(f_cuse, CUSE_IOCTL_FREE_UNIT, &num);
358
if (error)
359
return (CUSE_ERR_NO_MEMORY);
360
361
return (0);
362
}
363
364
struct cuse_dev *
365
cuse_dev_create(const struct cuse_methods *mtod, void *priv0, void *priv1,
366
uid_t _uid, gid_t _gid, int _perms, const char *_fmt,...)
367
{
368
struct cuse_create_dev info;
369
struct cuse_dev *cdev;
370
va_list args;
371
int error;
372
373
if (f_cuse < 0)
374
return (NULL);
375
376
cdev = malloc(sizeof(*cdev));
377
if (cdev == NULL)
378
return (NULL);
379
380
memset(cdev, 0, sizeof(*cdev));
381
382
cdev->mtod = mtod;
383
cdev->priv0 = priv0;
384
cdev->priv1 = priv1;
385
386
memset(&info, 0, sizeof(info));
387
388
info.dev = cdev;
389
info.user_id = _uid;
390
info.group_id = _gid;
391
info.permissions = _perms;
392
393
va_start(args, _fmt);
394
vsnprintf(info.devname, sizeof(info.devname), _fmt, args);
395
va_end(args);
396
397
error = ioctl(f_cuse, CUSE_IOCTL_CREATE_DEV, &info);
398
if (error) {
399
free(cdev);
400
return (NULL);
401
}
402
CUSE_LOCK();
403
TAILQ_INSERT_TAIL(&h_cuse, cdev, entry);
404
CUSE_UNLOCK();
405
406
return (cdev);
407
}
408
409
410
void
411
cuse_dev_destroy(struct cuse_dev *cdev)
412
{
413
int error;
414
415
if (f_cuse < 0)
416
return;
417
418
CUSE_LOCK();
419
TAILQ_REMOVE(&h_cuse, cdev, entry);
420
CUSE_UNLOCK();
421
422
error = ioctl(f_cuse, CUSE_IOCTL_DESTROY_DEV, &cdev);
423
if (error)
424
return;
425
426
free(cdev);
427
}
428
429
void *
430
cuse_dev_get_priv0(struct cuse_dev *cdev)
431
{
432
return (cdev->priv0);
433
}
434
435
void *
436
cuse_dev_get_priv1(struct cuse_dev *cdev)
437
{
438
return (cdev->priv1);
439
}
440
441
void
442
cuse_dev_set_priv0(struct cuse_dev *cdev, void *priv)
443
{
444
cdev->priv0 = priv;
445
}
446
447
void
448
cuse_dev_set_priv1(struct cuse_dev *cdev, void *priv)
449
{
450
cdev->priv1 = priv;
451
}
452
453
int
454
cuse_wait_and_process(void)
455
{
456
pthread_t curr = pthread_self();
457
struct cuse_dev_entered *pe;
458
struct cuse_dev_entered enter;
459
struct cuse_command info;
460
struct cuse_dev *cdev;
461
int error;
462
463
if (f_cuse < 0)
464
return (CUSE_ERR_INVALID);
465
466
error = ioctl(f_cuse, CUSE_IOCTL_GET_COMMAND, &info);
467
if (error)
468
return (CUSE_ERR_OTHER);
469
470
cdev = info.dev;
471
472
CUSE_LOCK();
473
enter.thread = curr;
474
enter.per_file_handle = (void *)info.per_file_handle;
475
enter.cmd = info.command;
476
enter.is_local = 0;
477
enter.got_signal = 0;
478
enter.cdev = cdev;
479
TAILQ_INSERT_TAIL(&h_cuse_entered, &enter, entry);
480
CUSE_UNLOCK();
481
482
DPRINTF("cuse: Command = %d = %s, flags = %d, arg = 0x%08x, ptr = 0x%08x\n",
483
(int)info.command, cuse_cmd_str(info.command), (int)info.fflags,
484
(int)info.argument, (int)info.data_pointer);
485
486
switch (info.command) {
487
case CUSE_CMD_OPEN:
488
if (cdev->mtod->cm_open != NULL)
489
error = (cdev->mtod->cm_open) (cdev, (int)info.fflags);
490
else
491
error = 0;
492
break;
493
494
case CUSE_CMD_CLOSE:
495
496
/* wait for other threads to stop */
497
498
while (1) {
499
500
error = 0;
501
502
CUSE_LOCK();
503
TAILQ_FOREACH(pe, &h_cuse_entered, entry) {
504
if (pe->cdev != cdev)
505
continue;
506
if (pe->thread == curr)
507
continue;
508
if (pe->per_file_handle !=
509
enter.per_file_handle)
510
continue;
511
pe->got_signal = 1;
512
pthread_kill(pe->thread, SIGHUP);
513
error = CUSE_ERR_BUSY;
514
}
515
CUSE_UNLOCK();
516
517
if (error == 0)
518
break;
519
else
520
usleep(10000);
521
}
522
523
if (cdev->mtod->cm_close != NULL)
524
error = (cdev->mtod->cm_close) (cdev, (int)info.fflags);
525
else
526
error = 0;
527
break;
528
529
case CUSE_CMD_READ:
530
if (cdev->mtod->cm_read != NULL) {
531
error = (cdev->mtod->cm_read) (cdev, (int)info.fflags,
532
(void *)info.data_pointer, (int)info.argument);
533
} else {
534
error = CUSE_ERR_INVALID;
535
}
536
break;
537
538
case CUSE_CMD_WRITE:
539
if (cdev->mtod->cm_write != NULL) {
540
error = (cdev->mtod->cm_write) (cdev, (int)info.fflags,
541
(void *)info.data_pointer, (int)info.argument);
542
} else {
543
error = CUSE_ERR_INVALID;
544
}
545
break;
546
547
case CUSE_CMD_IOCTL:
548
if (cdev->mtod->cm_ioctl != NULL) {
549
error = (cdev->mtod->cm_ioctl) (cdev, (int)info.fflags,
550
(unsigned int)info.argument, (void *)info.data_pointer);
551
} else {
552
error = CUSE_ERR_INVALID;
553
}
554
break;
555
556
case CUSE_CMD_POLL:
557
if (cdev->mtod->cm_poll != NULL) {
558
error = (cdev->mtod->cm_poll) (cdev, (int)info.fflags,
559
(int)info.argument);
560
} else {
561
error = CUSE_POLL_ERROR;
562
}
563
break;
564
565
case CUSE_CMD_SIGNAL:
566
CUSE_LOCK();
567
TAILQ_FOREACH(pe, &h_cuse_entered, entry) {
568
if (pe->cdev != cdev)
569
continue;
570
if (pe->thread == curr)
571
continue;
572
if (pe->per_file_handle !=
573
enter.per_file_handle)
574
continue;
575
pe->got_signal = 1;
576
pthread_kill(pe->thread, SIGHUP);
577
}
578
CUSE_UNLOCK();
579
break;
580
581
default:
582
error = CUSE_ERR_INVALID;
583
break;
584
}
585
586
DPRINTF("cuse: Command error = %d for %s\n",
587
error, cuse_cmd_str(info.command));
588
589
CUSE_LOCK();
590
TAILQ_REMOVE(&h_cuse_entered, &enter, entry);
591
CUSE_UNLOCK();
592
593
/* we ignore any sync command failures */
594
ioctl(f_cuse, CUSE_IOCTL_SYNC_COMMAND, &error);
595
596
return (0);
597
}
598
599
static struct cuse_dev_entered *
600
cuse_dev_get_entered(void)
601
{
602
struct cuse_dev_entered *pe;
603
pthread_t curr = pthread_self();
604
605
CUSE_LOCK();
606
TAILQ_FOREACH(pe, &h_cuse_entered, entry) {
607
if (pe->thread == curr)
608
break;
609
}
610
CUSE_UNLOCK();
611
return (pe);
612
}
613
614
void
615
cuse_dev_set_per_file_handle(struct cuse_dev *cdev, void *handle)
616
{
617
struct cuse_dev_entered *pe;
618
619
pe = cuse_dev_get_entered();
620
if (pe == NULL || pe->cdev != cdev)
621
return;
622
623
pe->per_file_handle = handle;
624
ioctl(f_cuse, CUSE_IOCTL_SET_PFH, &handle);
625
}
626
627
void *
628
cuse_dev_get_per_file_handle(struct cuse_dev *cdev)
629
{
630
struct cuse_dev_entered *pe;
631
632
pe = cuse_dev_get_entered();
633
if (pe == NULL || pe->cdev != cdev)
634
return (NULL);
635
636
return (pe->per_file_handle);
637
}
638
639
void
640
cuse_set_local(int val)
641
{
642
struct cuse_dev_entered *pe;
643
644
pe = cuse_dev_get_entered();
645
if (pe == NULL)
646
return;
647
648
pe->is_local = val;
649
}
650
651
#ifdef HAVE_DEBUG
652
static const char *
653
cuse_cmd_str(int cmd)
654
{
655
static const char *str[CUSE_CMD_MAX] = {
656
[CUSE_CMD_NONE] = "none",
657
[CUSE_CMD_OPEN] = "open",
658
[CUSE_CMD_CLOSE] = "close",
659
[CUSE_CMD_READ] = "read",
660
[CUSE_CMD_WRITE] = "write",
661
[CUSE_CMD_IOCTL] = "ioctl",
662
[CUSE_CMD_POLL] = "poll",
663
[CUSE_CMD_SIGNAL] = "signal",
664
[CUSE_CMD_SYNC] = "sync",
665
};
666
667
if ((cmd >= 0) && (cmd < CUSE_CMD_MAX) &&
668
(str[cmd] != NULL))
669
return (str[cmd]);
670
else
671
return ("unknown");
672
}
673
674
#endif
675
676
int
677
cuse_get_local(void)
678
{
679
struct cuse_dev_entered *pe;
680
681
pe = cuse_dev_get_entered();
682
if (pe == NULL)
683
return (0);
684
685
return (pe->is_local);
686
}
687
688
int
689
cuse_copy_out(const void *src, void *user_dst, int len)
690
{
691
struct cuse_data_chunk info;
692
struct cuse_dev_entered *pe;
693
int error;
694
695
if ((f_cuse < 0) || (len < 0))
696
return (CUSE_ERR_INVALID);
697
698
pe = cuse_dev_get_entered();
699
if (pe == NULL)
700
return (CUSE_ERR_INVALID);
701
702
DPRINTF("cuse: copy_out(%p,%p,%d), cmd = %d = %s\n",
703
src, user_dst, len, pe->cmd, cuse_cmd_str(pe->cmd));
704
705
if (pe->is_local) {
706
memcpy(user_dst, src, len);
707
} else {
708
info.local_ptr = (uintptr_t)src;
709
info.peer_ptr = (uintptr_t)user_dst;
710
info.length = len;
711
712
error = ioctl(f_cuse, CUSE_IOCTL_WRITE_DATA, &info);
713
if (error) {
714
DPRINTF("cuse: copy_out() error = %d\n", errno);
715
return (CUSE_ERR_FAULT);
716
}
717
}
718
return (0);
719
}
720
721
int
722
cuse_copy_in(const void *user_src, void *dst, int len)
723
{
724
struct cuse_data_chunk info;
725
struct cuse_dev_entered *pe;
726
int error;
727
728
if ((f_cuse < 0) || (len < 0))
729
return (CUSE_ERR_INVALID);
730
731
pe = cuse_dev_get_entered();
732
if (pe == NULL)
733
return (CUSE_ERR_INVALID);
734
735
DPRINTF("cuse: copy_in(%p,%p,%d), cmd = %d = %s\n",
736
user_src, dst, len, pe->cmd, cuse_cmd_str(pe->cmd));
737
738
if (pe->is_local) {
739
memcpy(dst, user_src, len);
740
} else {
741
info.local_ptr = (uintptr_t)dst;
742
info.peer_ptr = (uintptr_t)user_src;
743
info.length = len;
744
745
error = ioctl(f_cuse, CUSE_IOCTL_READ_DATA, &info);
746
if (error) {
747
DPRINTF("cuse: copy_in() error = %d\n", errno);
748
return (CUSE_ERR_FAULT);
749
}
750
}
751
return (0);
752
}
753
754
struct cuse_dev *
755
cuse_dev_get_current(int *pcmd)
756
{
757
struct cuse_dev_entered *pe;
758
759
pe = cuse_dev_get_entered();
760
if (pe == NULL) {
761
if (pcmd != NULL)
762
*pcmd = 0;
763
return (NULL);
764
}
765
if (pcmd != NULL)
766
*pcmd = pe->cmd;
767
return (pe->cdev);
768
}
769
770
int
771
cuse_got_peer_signal(void)
772
{
773
struct cuse_dev_entered *pe;
774
775
pe = cuse_dev_get_entered();
776
if (pe == NULL)
777
return (CUSE_ERR_INVALID);
778
779
if (pe->got_signal)
780
return (0);
781
782
return (CUSE_ERR_OTHER);
783
}
784
785
void
786
cuse_poll_wakeup(void)
787
{
788
int error = 0;
789
790
if (f_cuse < 0)
791
return;
792
793
ioctl(f_cuse, CUSE_IOCTL_SELWAKEUP, &error);
794
}
795
796