-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathfs.c
3061 lines (2710 loc) · 75.7 KB
/
fs.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2016 Jakub Klama <[email protected]>
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* Based on libixp code: ©2007-2010 Kris Maglione <maglione.k at Gmail>
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <pthread.h>
#include "../lib9p.h"
#include "../lib9p_impl.h"
#include "../fid.h"
#include "../log.h"
#include "../rfuncs.h"
#include "../genacl.h"
#include "backend.h"
#include "fs.h"
#if defined(WITH_CASPER)
#include <libcasper.h>
#include <casper/cap_pwd.h>
#include <casper/cap_grp.h>
#endif
#if defined(__FreeBSD__)
#include <sys/param.h>
#if __FreeBSD_version >= 1000000
#define HAVE_BINDAT
#endif
#endif
#if defined(__FreeBSD__)
#define HAVE_BIRTHTIME
#endif
#if defined(__APPLE__)
#include <sys/syscall.h>
#include "Availability.h"
#define ACL_TYPE_NFS4 ACL_TYPE_EXTENDED
#endif
struct fs_softc {
int fs_rootfd;
bool fs_readonly;
#if defined(WITH_CASPER)
cap_channel_t *fs_cappwd;
cap_channel_t *fs_capgrp;
#endif
};
struct fs_fid {
DIR *ff_dir;
int ff_dirfd;
int ff_fd;
int ff_flags;
char *ff_name;
struct fs_authinfo *ff_ai;
pthread_mutex_t ff_mtx;
struct l9p_acl *ff_acl; /* cached ACL if any */
};
#define FF_NO_NFSV4_ACL 0x01 /* don't go looking for NFSv4 ACLs */
/* FF_NO_POSIX_ACL 0x02 -- not yet */
/*
* Our authinfo consists of:
*
* - a reference count
* - a uid
* - a gid-set
*
* The "default" gid is the first gid in the git-set, provided the
* set size is at least 1. The set-size may be zero, though.
*
* Adjustments to the ref-count must be atomic, once it's shared.
* It would be nice to use C11 atomics here but they are not common
* enough to all systems just yet; for now, we use a mutex.
*
* Note that some ops (Linux style ones) pass an effective gid for
* the op, in which case, that gid may override. To achieve this
* effect, permissions testing functions also take an extra gid.
* If this gid is (gid_t)-1 it is not used and only the remaining
* gids take part.
*
* The uid may also be (uid_t)-1, meaning "no uid was available
* at all at attach time". In this case, new files inherit parent
* directory uids.
*
* The refcount is simply the number of "openfile"s using this
* authinfo (so that when the last ref goes away, we can free it).
*
* There are also master ACL flags (same as in ff_flags).
*/
struct fs_authinfo {
pthread_mutex_t ai_mtx; /* lock for refcnt */
uint32_t ai_refcnt;
int ai_flags;
uid_t ai_uid;
int ai_ngids;
gid_t ai_gids[]; /* NB: flexible array member */
};
/*
* We have a global-static mutex for single-threading Tattach
* requests, which use getpwnam (and indirectly, getgr* functions)
* which are not reentrant.
*/
static bool fs_attach_mutex_inited;
static pthread_mutex_t fs_attach_mutex;
/*
* Internal functions (except inline functions).
*/
static struct passwd *fs_getpwuid(struct fs_softc *, uid_t, struct r_pgdata *);
static struct group *fs_getgrgid(struct fs_softc *, gid_t, struct r_pgdata *);
static int fs_buildname(struct l9p_fid *, char *, char *, size_t);
static int fs_pdir(struct fs_softc *, struct l9p_fid *, char *, size_t,
struct stat *st);
static int fs_dpf(char *, char *, size_t);
static int fs_oflags_dotu(int, int *);
static int fs_oflags_dotl(uint32_t, int *, enum l9p_omode *);
static int fs_nde(struct fs_softc *, struct l9p_fid *, bool, gid_t,
struct stat *, uid_t *, gid_t *);
static struct fs_fid *open_fid(int, const char *, struct fs_authinfo *, bool);
static void dostat(struct fs_softc *, struct l9p_stat *, char *,
struct stat *, bool dotu);
static void dostatfs(struct l9p_statfs *, struct statfs *, long);
static void fillacl(struct fs_fid *ff);
static struct l9p_acl *getacl(struct fs_fid *ff, int fd, const char *path);
static void dropacl(struct fs_fid *ff);
static struct l9p_acl *look_for_nfsv4_acl(struct fs_fid *ff, int fd,
const char *path);
static int check_access(int32_t,
struct l9p_acl *, struct stat *, struct l9p_acl *, struct stat *,
struct fs_authinfo *, gid_t);
static void generate_qid(struct stat *, struct l9p_qid *);
static int fs_icreate(void *, struct l9p_fid *, char *, int,
bool, mode_t, gid_t, struct stat *);
static int fs_iopen(void *, struct l9p_fid *, int, enum l9p_omode,
gid_t, struct stat *);
static int fs_imkdir(void *, struct l9p_fid *, char *,
bool, mode_t, gid_t, struct stat *);
static int fs_imkfifo(void *, struct l9p_fid *, char *,
bool, mode_t, gid_t, struct stat *);
static int fs_imknod(void *, struct l9p_fid *, char *,
bool, mode_t, dev_t, gid_t, struct stat *);
static int fs_imksocket(void *, struct l9p_fid *, char *,
bool, mode_t, gid_t, struct stat *);
static int fs_isymlink(void *, struct l9p_fid *, char *, char *,
gid_t, struct stat *);
/*
* Internal functions implementing backend.
*/
static int fs_attach(void *, struct l9p_request *);
static int fs_clunk(void *, struct l9p_fid *);
static int fs_create(void *, struct l9p_request *);
static int fs_open(void *, struct l9p_request *);
static int fs_read(void *, struct l9p_request *);
static int fs_remove(void *, struct l9p_fid *);
static int fs_stat(void *, struct l9p_request *);
static int fs_walk(void *, struct l9p_request *);
static int fs_write(void *, struct l9p_request *);
static int fs_wstat(void *, struct l9p_request *);
static int fs_statfs(void *, struct l9p_request *);
static int fs_lopen(void *, struct l9p_request *);
static int fs_lcreate(void *, struct l9p_request *);
static int fs_symlink(void *, struct l9p_request *);
static int fs_mknod(void *, struct l9p_request *);
static int fs_rename(void *, struct l9p_request *);
static int fs_readlink(void *, struct l9p_request *);
static int fs_getattr(void *, struct l9p_request *);
static int fs_setattr(void *, struct l9p_request *);
static int fs_xattrwalk(void *, struct l9p_request *);
static int fs_xattrcreate(void *, struct l9p_request *);
static int fs_readdir(void *, struct l9p_request *);
static int fs_fsync(void *, struct l9p_request *);
static int fs_lock(void *, struct l9p_request *);
static int fs_getlock(void *, struct l9p_request *);
static int fs_link(void *, struct l9p_request *);
static int fs_renameat(void *, struct l9p_request *);
static int fs_unlinkat(void *, struct l9p_request *);
static void fs_freefid(void *, struct l9p_fid *);
/*
* Convert from 9p2000 open/create mode to Unix-style O_* flags.
* This includes 9p2000.u extensions, but not 9p2000.L protocol,
* which has entirely different open, create, etc., flag bits.
*
* The <mode> given here is the one-byte (uint8_t) "mode"
* argument to Tcreate or Topen, so it can have at most 8 bits.
*
* https://swtch.com/plan9port/man/man9/open.html and
* http://plan9.bell-labs.com/magic/man2html/5/open
* both say:
*
* The [low two bits of the] mode field determines the
* type of I/O ... [I]f mode has the OTRUNC (0x10) bit
* set, the file is to be truncated, which requires write
* permission ...; if the mode has the ORCLOSE (0x40) bit
* set, the file is to be removed when the fid is clunked,
* which requires permission to remove the file from its
* directory. All other bits in mode should be zero. It
* is illegal to write a directory, truncate it, or
* attempt to remove it on close.
*
* 9P2000.u may add ODIRECT (0x80); this is not completely clear.
* The fcall.h header defines OCEXEC (0x20) as well, but it makes
* no sense to send this to a server. There seem to be no bits
* 0x04 and 0x08.
*
* We always turn on O_NOCTTY since as a server, we never want
* to gain a controlling terminal. We always turn on O_NOFOLLOW
* for reasons described elsewhere.
*/
static int
fs_oflags_dotu(int mode, int *aflags)
{
int flags;
#define CONVERT(theirs, ours) \
do { \
if (mode & (theirs)) { \
mode &= ~(theirs); \
flags |= ours; \
} \
} while (0)
switch (mode & L9P_OACCMODE) {
case L9P_OREAD:
default:
flags = O_RDONLY;
break;
case L9P_OWRITE:
flags = O_WRONLY;
break;
case L9P_ORDWR:
flags = O_RDWR;
break;
case L9P_OEXEC:
if (mode & L9P_OTRUNC)
return (EINVAL);
flags = O_RDONLY;
break;
}
flags |= O_NOCTTY | O_NOFOLLOW;
CONVERT(L9P_OTRUNC, O_TRUNC);
/*
* Now take away some flags locally:
* the access mode (already translated)
* ORCLOSE - caller only
* OCEXEC - makes no sense in server
* ODIRECT - not applicable here
* If there are any flag bits left after this,
* we were unable to translate them. For now, let's
* treat this as EINVAL so that we can catch problems.
*/
mode &= ~(L9P_OACCMODE | L9P_ORCLOSE | L9P_OCEXEC | L9P_ODIRECT);
if (mode != 0) {
L9P_LOG(L9P_INFO,
"fs_oflags_dotu: untranslated bits: %#x",
(unsigned)mode);
return (EINVAL);
}
*aflags = flags;
return (0);
#undef CONVERT
}
/*
* Convert from 9P2000.L (Linux) open mode bits to O_* flags.
* See fs_oflags_dotu above.
*
* Linux currently does not have open-for-exec, but there is a
* proposal for it using O_PATH|O_NOFOLLOW, now handled here.
*
* We may eventually also set L9P_ORCLOSE for L_O_TMPFILE.
*/
static int
fs_oflags_dotl(uint32_t l_mode, int *aflags, enum l9p_omode *ap9)
{
int flags;
enum l9p_omode p9;
#define CLEAR(theirs) l_mode &= ~(uint32_t)(theirs)
#define CONVERT(theirs, ours) \
do { \
if (l_mode & (theirs)) { \
CLEAR(theirs); \
flags |= ours; \
} \
} while (0)
/*
* Linux O_RDONLY, O_WRONLY, O_RDWR (0,1,2) match BSD/MacOS.
*/
flags = l_mode & O_ACCMODE;
if (flags == 3)
return (EINVAL);
CLEAR(O_ACCMODE);
if ((l_mode & (L9P_L_O_PATH | L9P_L_O_NOFOLLOW)) ==
(L9P_L_O_PATH | L9P_L_O_NOFOLLOW)) {
CLEAR(L9P_L_O_PATH | L9P_L_O_NOFOLLOW);
p9 = L9P_OEXEC;
} else {
/*
* Slightly dirty, but same dirt, really, as
* setting flags from l_mode & O_ACCMODE.
*/
p9 = (enum l9p_omode)flags; /* slightly dirty */
}
/* turn L_O_TMPFILE into L9P_ORCLOSE in *p9? */
if (l_mode & L9P_L_O_TRUNC)
p9 |= L9P_OTRUNC; /* but don't CLEAR yet */
flags |= O_NOCTTY | O_NOFOLLOW;
/*
* L_O_CREAT seems to be noise, since we get separate open
* and create. But it is actually set sometimes. We just
* throw it out here; create ops must set it themselves and
* open ops have no permissions bits and hence cannot create.
*
* L_O_EXCL does make sense on create ops, i.e., we can
* take a create op with or without L_O_EXCL. We pass that
* through.
*/
CLEAR(L9P_L_O_CREAT);
CONVERT(L9P_L_O_EXCL, O_EXCL);
CONVERT(L9P_L_O_TRUNC, O_TRUNC);
CONVERT(L9P_L_O_DIRECTORY, O_DIRECTORY);
CONVERT(L9P_L_O_APPEND, O_APPEND);
CONVERT(L9P_L_O_NONBLOCK, O_NONBLOCK);
/*
* Discard these as useless noise at our (server) end.
* (NOATIME might be useful but we can only set it on a
* per-mount basis.)
*/
CLEAR(L9P_L_O_CLOEXEC);
CLEAR(L9P_L_O_DIRECT);
CLEAR(L9P_L_O_DSYNC);
CLEAR(L9P_L_O_FASYNC);
CLEAR(L9P_L_O_LARGEFILE);
CLEAR(L9P_L_O_NOATIME);
CLEAR(L9P_L_O_NOCTTY);
CLEAR(L9P_L_O_NOFOLLOW);
CLEAR(L9P_L_O_SYNC);
if (l_mode != 0) {
L9P_LOG(L9P_INFO,
"fs_oflags_dotl: untranslated bits: %#x",
(unsigned)l_mode);
return (EINVAL);
}
*aflags = flags;
*ap9 = p9;
return (0);
#undef CLEAR
#undef CONVERT
}
static struct passwd *
fs_getpwuid(struct fs_softc *sc, uid_t uid, struct r_pgdata *pg)
{
#if defined(WITH_CASPER)
return (r_cap_getpwuid(sc->fs_cappwd, uid, pg));
#else
(void)sc;
return (r_getpwuid(uid, pg));
#endif
}
static struct group *
fs_getgrgid(struct fs_softc *sc, gid_t gid, struct r_pgdata *pg)
{
#if defined(WITH_CASPER)
return (r_cap_getgrgid(sc->fs_capgrp, gid, pg));
#else
(void)sc;
return (r_getgrgid(gid, pg));
#endif
}
/*
* Build full name of file by appending given name to directory name.
*/
static int
fs_buildname(struct l9p_fid *dir, char *name, char *buf, size_t size)
{
struct fs_fid *dirf = dir->lo_aux;
size_t dlen, nlen1;
assert(dirf != NULL);
dlen = strlen(dirf->ff_name);
nlen1 = strlen(name) + 1; /* +1 for '\0' */
if (dlen + 1 + nlen1 > size)
return (ENAMETOOLONG);
memcpy(buf, dirf->ff_name, dlen);
buf[dlen] = '/';
memcpy(buf + dlen + 1, name, nlen1);
return (0);
}
/*
* Build parent name of file by splitting it off. Return an error
* if the given fid represents the root, so that there is no such
* parent, or if the discovered parent is not a directory.
*/
static int
fs_pdir(struct fs_softc *sc __unused, struct l9p_fid *fid, char *buf,
size_t size, struct stat *st)
{
struct fs_fid *ff;
char *path;
ff = fid->lo_aux;
assert(ff != NULL);
path = ff->ff_name;
path = r_dirname(path, buf, size);
if (path == NULL)
return (ENAMETOOLONG);
if (fstatat(ff->ff_dirfd, path, st, AT_SYMLINK_NOFOLLOW) != 0)
return (errno);
if (!S_ISDIR(st->st_mode))
return (ENOTDIR);
return (0);
}
/*
* Like fs_buildname() but for adding a file name to a buffer
* already holding a directory name. Essentially does
* strcat(dbuf, "/");
* strcat(dbuf, fname);
* but with size checking and an ENAMETOOLONG error as needed.
*
* (Think of the function name as "directory plus-equals file".)
*/
static int
fs_dpf(char *dbuf, char *fname, size_t size)
{
size_t dlen, nlen1;
dlen = strlen(dbuf);
nlen1 = strlen(fname) + 1;
if (dlen + 1 + nlen1 > size)
return (ENAMETOOLONG);
dbuf[dlen] = '/';
memcpy(dbuf + dlen + 1, fname, nlen1);
return (0);
}
/*
* Prepare to create a new directory entry (open with O_CREAT,
* mkdir, etc -- any operation that creates a new inode),
* operating in parent data <dir>, based on authinfo <ai> and
* effective gid <egid>.
*
* The new entity should be owned by user/group <*nuid, *ngid>,
* if it's really a new entity. It will be a directory if isdir.
*
* Returns an error number if the entry should not be created
* (e.g., read-only file system or no permission to write in
* parent directory). Always sets *nuid and *ngid on success:
* in the worst case, when there is no available ID, this will
* use the parent directory's IDs. Fills in <*st> on success.
*/
static int
fs_nde(struct fs_softc *sc, struct l9p_fid *dir, bool isdir, gid_t egid,
struct stat *st, uid_t *nuid, gid_t *ngid)
{
struct fs_fid *dirf;
struct fs_authinfo *ai;
int32_t op;
int error;
if (sc->fs_readonly)
return (EROFS);
dirf = dir->lo_aux;
assert(dirf != NULL);
if (fstatat(dirf->ff_dirfd, dirf->ff_name, st,
AT_SYMLINK_NOFOLLOW) != 0)
return (errno);
if (!S_ISDIR(st->st_mode))
return (ENOTDIR);
dirf = dir->lo_aux;
ai = dirf->ff_ai;
fillacl(dirf);
op = isdir ? L9P_ACE_ADD_SUBDIRECTORY : L9P_ACE_ADD_FILE;
error = check_access(op, dirf->ff_acl, st, NULL, NULL, ai, egid);
if (error)
return (EPERM);
*nuid = ai->ai_uid != (uid_t)-1 ? ai->ai_uid : st->st_uid;
*ngid = egid != (gid_t)-1 ? egid :
ai->ai_ngids > 0 ? ai->ai_gids[0] : st->st_gid;
return (0);
}
/*
* Allocate new open-file data structure to attach to a fid.
*
* The new file's authinfo is the same as the old one's, and
* we gain a reference.
*/
static struct fs_fid *
open_fid(int dirfd, const char *path, struct fs_authinfo *ai, bool creating)
{
struct fs_fid *ret;
uint32_t newcount;
int error;
ret = l9p_calloc(1, sizeof(*ret));
error = pthread_mutex_init(&ret->ff_mtx, NULL);
if (error) {
free(ret);
return (NULL);
}
ret->ff_fd = -1;
ret->ff_dirfd = dirfd;
ret->ff_name = strdup(path);
if (ret->ff_name == NULL) {
pthread_mutex_destroy(&ret->ff_mtx);
free(ret);
return (NULL);
}
pthread_mutex_lock(&ai->ai_mtx);
newcount = ++ai->ai_refcnt;
pthread_mutex_unlock(&ai->ai_mtx);
/*
* If we just incremented the count to 1, we're the *first*
* reference. This is only allowed when creating the authinfo,
* otherwise it means something has gone wrong. This cannot
* catch every bad (re)use of a freed authinfo but it may catch
* a few.
*/
assert(newcount > 1 || creating);
L9P_LOG(L9P_DEBUG, "authinfo %p now used by %lu",
(void *)ai, (u_long)newcount);
ret->ff_ai = ai;
return (ret);
}
static void
dostat(struct fs_softc *sc, struct l9p_stat *s, char *name,
struct stat *buf, bool dotu)
{
struct passwd *user;
struct group *group;
memset(s, 0, sizeof(struct l9p_stat));
generate_qid(buf, &s->qid);
s->type = 0;
s->dev = 0;
s->mode = buf->st_mode & 0777;
if (S_ISDIR(buf->st_mode))
s->mode |= L9P_DMDIR;
if (S_ISLNK(buf->st_mode) && dotu)
s->mode |= L9P_DMSYMLINK;
if (S_ISCHR(buf->st_mode) || S_ISBLK(buf->st_mode))
s->mode |= L9P_DMDEVICE;
if (S_ISSOCK(buf->st_mode))
s->mode |= L9P_DMSOCKET;
if (S_ISFIFO(buf->st_mode))
s->mode |= L9P_DMNAMEDPIPE;
s->atime = (uint32_t)buf->st_atime;
s->mtime = (uint32_t)buf->st_mtime;
s->length = (uint64_t)buf->st_size;
s->name = r_basename(name, NULL, 0);
if (!dotu) {
struct r_pgdata udata, gdata;
user = fs_getpwuid(sc, buf->st_uid, &udata);
group = fs_getgrgid(sc, buf->st_gid, &gdata);
s->uid = user != NULL ? strdup(user->pw_name) : NULL;
s->gid = group != NULL ? strdup(group->gr_name) : NULL;
s->muid = user != NULL ? strdup(user->pw_name) : NULL;
r_pgfree(&udata);
r_pgfree(&gdata);
} else {
/*
* When using 9P2000.u, we don't need to bother about
* providing user and group names in textual form.
*
* NB: if the asprintf()s fail, s->extension should
* be unset so we can ignore these.
*/
s->n_uid = buf->st_uid;
s->n_gid = buf->st_gid;
s->n_muid = buf->st_uid;
if (S_ISLNK(buf->st_mode)) {
char target[MAXPATHLEN];
ssize_t ret = readlink(name, target, MAXPATHLEN);
if (ret < 0) {
s->extension = NULL;
return;
}
s->extension = strndup(target, (size_t)ret);
}
if (S_ISBLK(buf->st_mode)) {
asprintf(&s->extension, "b %d %d", major(buf->st_rdev),
minor(buf->st_rdev));
}
if (S_ISCHR(buf->st_mode)) {
asprintf(&s->extension, "c %d %d", major(buf->st_rdev),
minor(buf->st_rdev));
}
}
}
static void dostatfs(struct l9p_statfs *out, struct statfs *in, long namelen)
{
out->type = L9P_FSTYPE;
out->bsize = in->f_bsize;
out->blocks = in->f_blocks;
out->bfree = in->f_bfree;
out->bavail = in->f_bavail;
out->files = in->f_files;
out->ffree = in->f_ffree;
out->namelen = (uint32_t)namelen;
out->fsid = ((uint64_t)in->f_fsid.val[0] << 32) |
(uint64_t)in->f_fsid.val[1];
}
static void
generate_qid(struct stat *buf, struct l9p_qid *qid)
{
qid->path = buf->st_ino;
qid->version = 0;
if (S_ISREG(buf->st_mode))
qid->type |= L9P_QTFILE;
if (S_ISDIR(buf->st_mode))
qid->type |= L9P_QTDIR;
if (S_ISLNK(buf->st_mode))
qid->type |= L9P_QTSYMLINK;
}
/*
* Fill in ff->ff_acl if it's not set yet. Skip if the "don't use
* ACLs" flag is set, and use the flag to remember failure so
* we don't bother retrying either.
*/
static void
fillacl(struct fs_fid *ff)
{
if (ff->ff_acl == NULL && (ff->ff_flags & FF_NO_NFSV4_ACL) == 0) {
ff->ff_acl = look_for_nfsv4_acl(ff, ff->ff_fd, ff->ff_name);
if (ff->ff_acl == NULL)
ff->ff_flags |= FF_NO_NFSV4_ACL;
}
}
/*
* Get an ACL given fd and/or path name. We check for the "don't get
* ACL" flag in the given ff_fid data structure first, but don't set
* the flag here. The fillacl() code is similar but will set the
* flag; it also uses the ff_fd and ff_name directly.
*
* (This is used to get ACLs for parent directories, for instance.)
*/
static struct l9p_acl *
getacl(struct fs_fid *ff, int fd, const char *path)
{
if (ff->ff_flags & FF_NO_NFSV4_ACL)
return (NULL);
return look_for_nfsv4_acl(ff, fd, path);
}
/*
* Drop cached ff->ff_acl, e.g., after moving from one directory to
* another, where inherited ACLs might change.
*/
static void
dropacl(struct fs_fid *ff)
{
l9p_acl_free(ff->ff_acl);
ff->ff_acl = NULL;
ff->ff_flags = ff->ff_ai->ai_flags;
}
/*
* Check to see if we can find NFSv4 ACLs for the given file.
* If we have an open fd, we can use that, otherwise we need
* to use the path.
*/
static struct l9p_acl *
look_for_nfsv4_acl(struct fs_fid *ff, int fd, const char *path)
{
struct l9p_acl *acl;
acl_t sysacl;
int doclose = 0;
if (fd < 0) {
fd = openat(ff->ff_dirfd, path, 0);
doclose = 1;
}
sysacl = acl_get_fd_np(fd, ACL_TYPE_NFS4);
if (sysacl == NULL) {
/*
* EINVAL means no NFSv4 ACLs apply for this file.
* Other error numbers indicate some kind of problem.
*/
if (errno != EINVAL) {
L9P_LOG(L9P_ERROR,
"error retrieving NFSv4 ACL from "
"fdesc %d (%s): %s", fd,
path, strerror(errno));
}
if (doclose)
close(fd);
return (NULL);
}
#if defined(HAVE_FREEBSD_ACLS)
acl = l9p_freebsd_nfsv4acl_to_acl(sysacl);
#else
acl = NULL; /* XXX need a l9p_darwin_acl_to_acl */
#endif
acl_free(sysacl);
if (doclose)
close(fd);
return (acl);
}
/*
* Verify that the user whose authinfo is in <ai> and effective
* group ID is <egid> ((gid_t)-1 means no egid supplied) has
* permission to do something.
*
* The "something" may be rather complex: we allow NFSv4 style
* operation masks here, and provide parent and child ACLs and
* stat data. At most one of pacl+pst and cacl+cst can be NULL,
* unless ACLs are not supported; then pacl and cacl can both
* be NULL but pst or cst must be non-NULL depending on the
* operation.
*/
static int
check_access(int32_t opmask,
struct l9p_acl *pacl, struct stat *pst,
struct l9p_acl *cacl, struct stat *cst,
struct fs_authinfo *ai, gid_t egid)
{
struct l9p_acl_check_args args;
/*
* If we have ACLs, use them exclusively, ignoring Unix
* permissions. Otherwise, fall back on stat st_mode
* bits, and allow super-user as well.
*/
args.aca_uid = ai->ai_uid;
args.aca_gid = egid;
args.aca_groups = ai->ai_gids;
args.aca_ngroups = (size_t)ai->ai_ngids;
args.aca_parent = pacl;
args.aca_pstat = pst;
args.aca_child = cacl;
args.aca_cstat = cst;
args.aca_aclmode = pacl == NULL && cacl == NULL
? L9P_ACM_STAT_MODE
: L9P_ACM_NFS_ACL | L9P_ACM_ZFS_ACL;
args.aca_superuser = true;
return (l9p_acl_check_access(opmask, &args));
}
static int
fs_attach(void *softc, struct l9p_request *req)
{
struct fs_authinfo *ai;
struct fs_softc *sc = (struct fs_softc *)softc;
struct fs_fid *file;
struct passwd *pwd;
struct stat st;
struct r_pgdata udata;
uint32_t n_uname;
gid_t *gids;
uid_t uid;
int error;
int ngroups;
assert(req->lr_fid != NULL);
/*
* Single-thread pwd/group related items. We have a reentrant
* r_getpwuid but not a reentrant r_getpwnam, and l9p_getgrlist
* may use non-reentrant C library getgr* routines.
*/
pthread_mutex_lock(&fs_attach_mutex);
n_uname = req->lr_req.tattach.n_uname;
if (n_uname != L9P_NONUNAME) {
uid = (uid_t)n_uname;
pwd = fs_getpwuid(sc, uid, &udata);
if (pwd == NULL)
L9P_LOG(L9P_DEBUG,
"Tattach: uid %ld: no such user", (long)uid);
} else {
uid = (uid_t)-1;
#if defined(WITH_CASPER)
pwd = cap_getpwnam(sc->fs_cappwd, req->lr_req.tattach.uname);
#else
pwd = getpwnam(req->lr_req.tattach.uname);
#endif
if (pwd == NULL)
L9P_LOG(L9P_DEBUG,
"Tattach: %s: no such user",
req->lr_req.tattach.uname);
}
/*
* If caller didn't give a numeric UID, pick it up from pwd
* if possible. If that doesn't work we can't continue.
*
* Note that pwd also supplies the group set. This assumes
* the server has the right mapping; this needs improvement.
* We do at least support ai->ai_ngids==0 properly now though.
*/
if (uid == (uid_t)-1 && pwd != NULL)
uid = pwd->pw_uid;
if (uid == (uid_t)-1)
error = EPERM;
else {
error = 0;
if (fstat(sc->fs_rootfd, &st) != 0)
error = errno;
else if (!S_ISDIR(st.st_mode))
error = ENOTDIR;
}
if (error) {
pthread_mutex_unlock(&fs_attach_mutex);
L9P_LOG(L9P_DEBUG,
"Tattach: denying uid=%ld access to rootdir: %s",
(long)uid, strerror(error));
/*
* Pass ENOENT and ENOTDIR through for diagnosis;
* others become EPERM. This should not leak too
* much security.
*/
return (error == ENOENT || error == ENOTDIR ? error : EPERM);
}
if (pwd != NULL) {
/*
* This either succeeds and fills in ngroups and
* returns non-NULL, or fails and sets ngroups to 0
* and returns NULL. Either way ngroups is correct.
*/
gids = l9p_getgrlist(pwd->pw_name, pwd->pw_gid, &ngroups);
} else {
gids = NULL;
ngroups = 0;
}
/*
* Done with pwd and group related items that may use
* non-reentrant C library routines; allow other threads in.
*/
pthread_mutex_unlock(&fs_attach_mutex);
ai = malloc(sizeof(*ai) + (size_t)ngroups * sizeof(gid_t));
if (ai == NULL) {
free(gids);
return (ENOMEM);
}
error = pthread_mutex_init(&ai->ai_mtx, NULL);
if (error) {
free(gids);
free(ai);
return (error);
}
ai->ai_refcnt = 0;
ai->ai_uid = uid;
ai->ai_flags = 0; /* XXX for now */
ai->ai_ngids = ngroups;
memcpy(ai->ai_gids, gids, (size_t)ngroups * sizeof(gid_t));
free(gids);
file = open_fid(sc->fs_rootfd, ".", ai, true);
if (file == NULL) {
pthread_mutex_destroy(&ai->ai_mtx);
free(ai);
return (ENOMEM);
}
req->lr_fid->lo_aux = file;
generate_qid(&st, &req->lr_resp.rattach.qid);
return (0);
}
static int
fs_clunk(void *softc __unused, struct l9p_fid *fid)
{
struct fs_fid *file;
file = fid->lo_aux;
assert(file != NULL);
if (file->ff_dir) {
closedir(file->ff_dir);
file->ff_dir = NULL;
} else if (file->ff_fd != -1) {
close(file->ff_fd);
file->ff_fd = -1;
}
return (0);
}
/*
* Create ops.
*
* We are to create a new file under some existing path,
* where the new file's name is in the Tcreate request and the
* existing path is due to a fid-based file (req->lr_fid).
*
* One op (create regular file) sets file->fd, the rest do not.
*/
static int
fs_create(void *softc, struct l9p_request *req)
{
struct l9p_fid *dir;
struct stat st;
uint32_t dmperm;