1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
2f08c3bdfSopenharmony_ci/*
3f08c3bdfSopenharmony_ci * Copyright (c) 2021 Collabora Ltd.
4f08c3bdfSopenharmony_ci *
5f08c3bdfSopenharmony_ci * Author: Gabriel Krisman Bertazi <gabriel@krisman.be>
6f08c3bdfSopenharmony_ci * Based on previous work by Amir Goldstein <amir73il@gmail.com>
7f08c3bdfSopenharmony_ci */
8f08c3bdfSopenharmony_ci
9f08c3bdfSopenharmony_ci/*\
10f08c3bdfSopenharmony_ci * [Description]
11f08c3bdfSopenharmony_ci * Check fanotify FAN_ERROR_FS events triggered by intentionally
12f08c3bdfSopenharmony_ci * corrupted filesystems:
13f08c3bdfSopenharmony_ci *
14f08c3bdfSopenharmony_ci * - Generate a broken filesystem
15f08c3bdfSopenharmony_ci * - Start FAN_FS_ERROR monitoring group
16f08c3bdfSopenharmony_ci * - Make the file system notice the error through ordinary operations
17f08c3bdfSopenharmony_ci * - Observe the event generated
18f08c3bdfSopenharmony_ci */
19f08c3bdfSopenharmony_ci
20f08c3bdfSopenharmony_ci#define _GNU_SOURCE
21f08c3bdfSopenharmony_ci#include "config.h"
22f08c3bdfSopenharmony_ci
23f08c3bdfSopenharmony_ci#include <stdio.h>
24f08c3bdfSopenharmony_ci#include <sys/types.h>
25f08c3bdfSopenharmony_ci#include <errno.h>
26f08c3bdfSopenharmony_ci#include <string.h>
27f08c3bdfSopenharmony_ci#include <sys/mount.h>
28f08c3bdfSopenharmony_ci#include <sys/syscall.h>
29f08c3bdfSopenharmony_ci#include "tst_test.h"
30f08c3bdfSopenharmony_ci#include <sys/fanotify.h>
31f08c3bdfSopenharmony_ci#include <sys/types.h>
32f08c3bdfSopenharmony_ci
33f08c3bdfSopenharmony_ci#ifdef HAVE_SYS_FANOTIFY_H
34f08c3bdfSopenharmony_ci#include "fanotify.h"
35f08c3bdfSopenharmony_ci
36f08c3bdfSopenharmony_ci#ifndef EFSCORRUPTED
37f08c3bdfSopenharmony_ci#define EFSCORRUPTED    EUCLEAN         /* Filesystem is corrupted */
38f08c3bdfSopenharmony_ci#endif
39f08c3bdfSopenharmony_ci
40f08c3bdfSopenharmony_ci#define BUF_SIZE 256
41f08c3bdfSopenharmony_ci
42f08c3bdfSopenharmony_ci#define MOUNT_PATH "test_mnt"
43f08c3bdfSopenharmony_ci#define BASE_DIR "internal_dir"
44f08c3bdfSopenharmony_ci#define BAD_DIR BASE_DIR"/bad_dir"
45f08c3bdfSopenharmony_ci#define BAD_LINK BASE_DIR"/bad_link"
46f08c3bdfSopenharmony_ci
47f08c3bdfSopenharmony_ci#ifdef HAVE_NAME_TO_HANDLE_AT
48f08c3bdfSopenharmony_ci
49f08c3bdfSopenharmony_cistatic char event_buf[BUF_SIZE];
50f08c3bdfSopenharmony_cistatic int fd_notify;
51f08c3bdfSopenharmony_ci
52f08c3bdfSopenharmony_ci/* These expected FIDs are common to multiple tests */
53f08c3bdfSopenharmony_cistatic struct fanotify_fid_t null_fid;
54f08c3bdfSopenharmony_cistatic struct fanotify_fid_t bad_file_fid;
55f08c3bdfSopenharmony_cistatic struct fanotify_fid_t bad_link_fid;
56f08c3bdfSopenharmony_ci
57f08c3bdfSopenharmony_cistatic void trigger_fs_abort(void)
58f08c3bdfSopenharmony_ci{
59f08c3bdfSopenharmony_ci	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type,
60f08c3bdfSopenharmony_ci		   MS_REMOUNT|MS_RDONLY, "abort");
61f08c3bdfSopenharmony_ci}
62f08c3bdfSopenharmony_ci
63f08c3bdfSopenharmony_cistatic void do_debugfs_request(const char *dev, char *request)
64f08c3bdfSopenharmony_ci{
65f08c3bdfSopenharmony_ci	const char *const cmd[] = {"debugfs", "-w", dev, "-R", request, NULL};
66f08c3bdfSopenharmony_ci
67f08c3bdfSopenharmony_ci	SAFE_CMD(cmd, NULL, NULL);
68f08c3bdfSopenharmony_ci}
69f08c3bdfSopenharmony_ci
70f08c3bdfSopenharmony_cistatic void trigger_bad_file_lookup(void)
71f08c3bdfSopenharmony_ci{
72f08c3bdfSopenharmony_ci	int ret;
73f08c3bdfSopenharmony_ci
74f08c3bdfSopenharmony_ci	/* SAFE_OPEN cannot be used here because we expect it to fail. */
75f08c3bdfSopenharmony_ci	ret = open(MOUNT_PATH"/"BAD_DIR, O_RDONLY, 0);
76f08c3bdfSopenharmony_ci	if (ret != -1 && errno != EUCLEAN)
77f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Unexpected lookup result(%d) of %s (%d!=%d)",
78f08c3bdfSopenharmony_ci			ret, BAD_DIR, errno, EUCLEAN);
79f08c3bdfSopenharmony_ci}
80f08c3bdfSopenharmony_ci
81f08c3bdfSopenharmony_cistatic void trigger_bad_link_lookup(void)
82f08c3bdfSopenharmony_ci{
83f08c3bdfSopenharmony_ci	int ret;
84f08c3bdfSopenharmony_ci
85f08c3bdfSopenharmony_ci	/* SAFE_OPEN cannot be used here because we expect it to fail. */
86f08c3bdfSopenharmony_ci	ret = open(MOUNT_PATH"/"BAD_LINK, O_RDONLY, 0);
87f08c3bdfSopenharmony_ci	if (ret != -1 && errno != EUCLEAN)
88f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Unexpected open result(%d) of %s (%d!=%d)",
89f08c3bdfSopenharmony_ci			ret, BAD_LINK, errno, EUCLEAN);
90f08c3bdfSopenharmony_ci}
91f08c3bdfSopenharmony_ci
92f08c3bdfSopenharmony_ci
93f08c3bdfSopenharmony_cistatic void tcase3_trigger(void)
94f08c3bdfSopenharmony_ci{
95f08c3bdfSopenharmony_ci	trigger_bad_link_lookup();
96f08c3bdfSopenharmony_ci	trigger_bad_file_lookup();
97f08c3bdfSopenharmony_ci}
98f08c3bdfSopenharmony_ci
99f08c3bdfSopenharmony_cistatic void tcase4_trigger(void)
100f08c3bdfSopenharmony_ci{
101f08c3bdfSopenharmony_ci	trigger_bad_file_lookup();
102f08c3bdfSopenharmony_ci	trigger_fs_abort();
103f08c3bdfSopenharmony_ci}
104f08c3bdfSopenharmony_ci
105f08c3bdfSopenharmony_cistatic struct test_case {
106f08c3bdfSopenharmony_ci	char *name;
107f08c3bdfSopenharmony_ci	int error;
108f08c3bdfSopenharmony_ci	unsigned int error_count;
109f08c3bdfSopenharmony_ci	struct fanotify_fid_t *fid;
110f08c3bdfSopenharmony_ci	void (*trigger_error)(void);
111f08c3bdfSopenharmony_ci} testcases[] = {
112f08c3bdfSopenharmony_ci	{
113f08c3bdfSopenharmony_ci		.name = "Trigger abort",
114f08c3bdfSopenharmony_ci		.trigger_error = &trigger_fs_abort,
115f08c3bdfSopenharmony_ci		.error_count = 1,
116f08c3bdfSopenharmony_ci		.error = ESHUTDOWN,
117f08c3bdfSopenharmony_ci		.fid = &null_fid,
118f08c3bdfSopenharmony_ci	},
119f08c3bdfSopenharmony_ci	{
120f08c3bdfSopenharmony_ci		.name = "Lookup of inode with invalid mode",
121f08c3bdfSopenharmony_ci		.trigger_error = &trigger_bad_file_lookup,
122f08c3bdfSopenharmony_ci		.error_count = 1,
123f08c3bdfSopenharmony_ci		.error = EFSCORRUPTED,
124f08c3bdfSopenharmony_ci		.fid = &bad_file_fid,
125f08c3bdfSopenharmony_ci	},
126f08c3bdfSopenharmony_ci	{
127f08c3bdfSopenharmony_ci		.name = "Multiple error submission",
128f08c3bdfSopenharmony_ci		.trigger_error = &tcase3_trigger,
129f08c3bdfSopenharmony_ci		.error_count = 2,
130f08c3bdfSopenharmony_ci		.error = EFSCORRUPTED,
131f08c3bdfSopenharmony_ci		.fid = &bad_link_fid,
132f08c3bdfSopenharmony_ci	},
133f08c3bdfSopenharmony_ci	{
134f08c3bdfSopenharmony_ci		.name = "Multiple error submission 2",
135f08c3bdfSopenharmony_ci		.trigger_error = &tcase4_trigger,
136f08c3bdfSopenharmony_ci		.error_count = 2,
137f08c3bdfSopenharmony_ci		.error = EFSCORRUPTED,
138f08c3bdfSopenharmony_ci		.fid = &bad_file_fid,
139f08c3bdfSopenharmony_ci	}
140f08c3bdfSopenharmony_ci};
141f08c3bdfSopenharmony_ci
142f08c3bdfSopenharmony_cistatic int check_error_event_info_fid(struct fanotify_event_info_fid *fid,
143f08c3bdfSopenharmony_ci				 const struct test_case *ex)
144f08c3bdfSopenharmony_ci{
145f08c3bdfSopenharmony_ci	struct file_handle *fh = (struct file_handle *) &fid->handle;
146f08c3bdfSopenharmony_ci
147f08c3bdfSopenharmony_ci	if (memcmp(&fid->fsid, &ex->fid->fsid, sizeof(fid->fsid))) {
148f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Received bad FSID type (%x...!=%x...)",
149f08c3bdfSopenharmony_ci			ex->name, FSID_VAL_MEMBER(fid->fsid, 0),
150f08c3bdfSopenharmony_ci			ex->fid->fsid.val[0]);
151f08c3bdfSopenharmony_ci
152f08c3bdfSopenharmony_ci		return 1;
153f08c3bdfSopenharmony_ci	}
154f08c3bdfSopenharmony_ci	if (fh->handle_type != ex->fid->handle.handle_type) {
155f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Received bad file_handle type (%d!=%d)",
156f08c3bdfSopenharmony_ci			ex->name, fh->handle_type, ex->fid->handle.handle_type);
157f08c3bdfSopenharmony_ci		return 1;
158f08c3bdfSopenharmony_ci	}
159f08c3bdfSopenharmony_ci
160f08c3bdfSopenharmony_ci	if (fh->handle_bytes != ex->fid->handle.handle_bytes) {
161f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Received bad file_handle len (%d!=%d)",
162f08c3bdfSopenharmony_ci			ex->name, fh->handle_bytes, ex->fid->handle.handle_bytes);
163f08c3bdfSopenharmony_ci		return 1;
164f08c3bdfSopenharmony_ci	}
165f08c3bdfSopenharmony_ci
166f08c3bdfSopenharmony_ci	if (memcmp(fh->f_handle, ex->fid->handle.f_handle, fh->handle_bytes)) {
167f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Received wrong handle. "
168f08c3bdfSopenharmony_ci			"Expected (%x...) got (%x...) ", ex->name,
169f08c3bdfSopenharmony_ci			*(int *)ex->fid->handle.f_handle, *(int *)fh->f_handle);
170f08c3bdfSopenharmony_ci		return 1;
171f08c3bdfSopenharmony_ci	}
172f08c3bdfSopenharmony_ci	return 0;
173f08c3bdfSopenharmony_ci}
174f08c3bdfSopenharmony_ci
175f08c3bdfSopenharmony_cistatic int check_error_event_info_error(struct fanotify_event_info_error *info_error,
176f08c3bdfSopenharmony_ci				 const struct test_case *ex)
177f08c3bdfSopenharmony_ci{
178f08c3bdfSopenharmony_ci	int fail = 0;
179f08c3bdfSopenharmony_ci
180f08c3bdfSopenharmony_ci	if (info_error->error_count != ex->error_count) {
181f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Unexpected error_count (%d!=%d)",
182f08c3bdfSopenharmony_ci			ex->name, info_error->error_count, ex->error_count);
183f08c3bdfSopenharmony_ci		fail++;
184f08c3bdfSopenharmony_ci	}
185f08c3bdfSopenharmony_ci
186f08c3bdfSopenharmony_ci	if (info_error->error != ex->error) {
187f08c3bdfSopenharmony_ci		tst_res(TFAIL, "%s: Unexpected error code value (%d!=%d)",
188f08c3bdfSopenharmony_ci			ex->name, info_error->error, ex->error);
189f08c3bdfSopenharmony_ci		fail++;
190f08c3bdfSopenharmony_ci	}
191f08c3bdfSopenharmony_ci
192f08c3bdfSopenharmony_ci	return fail;
193f08c3bdfSopenharmony_ci}
194f08c3bdfSopenharmony_ci
195f08c3bdfSopenharmony_cistatic int check_error_event_metadata(struct fanotify_event_metadata *event)
196f08c3bdfSopenharmony_ci{
197f08c3bdfSopenharmony_ci	int fail = 0;
198f08c3bdfSopenharmony_ci
199f08c3bdfSopenharmony_ci	if (event->mask != FAN_FS_ERROR) {
200f08c3bdfSopenharmony_ci		fail++;
201f08c3bdfSopenharmony_ci		tst_res(TFAIL, "got unexpected event %llx",
202f08c3bdfSopenharmony_ci			(unsigned long long)event->mask);
203f08c3bdfSopenharmony_ci	}
204f08c3bdfSopenharmony_ci
205f08c3bdfSopenharmony_ci	if (event->fd != FAN_NOFD) {
206f08c3bdfSopenharmony_ci		fail++;
207f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Weird FAN_FD %llx",
208f08c3bdfSopenharmony_ci			(unsigned long long)event->mask);
209f08c3bdfSopenharmony_ci	}
210f08c3bdfSopenharmony_ci	return fail;
211f08c3bdfSopenharmony_ci}
212f08c3bdfSopenharmony_ci
213f08c3bdfSopenharmony_cistatic void check_event(char *buf, size_t len, const struct test_case *ex)
214f08c3bdfSopenharmony_ci{
215f08c3bdfSopenharmony_ci	struct fanotify_event_metadata *event =
216f08c3bdfSopenharmony_ci		(struct fanotify_event_metadata *) buf;
217f08c3bdfSopenharmony_ci	struct fanotify_event_info_error *info_error;
218f08c3bdfSopenharmony_ci	struct fanotify_event_info_fid *info_fid;
219f08c3bdfSopenharmony_ci	int fail = 0;
220f08c3bdfSopenharmony_ci
221f08c3bdfSopenharmony_ci	if (len < FAN_EVENT_METADATA_LEN) {
222f08c3bdfSopenharmony_ci		tst_res(TFAIL, "No event metadata found");
223f08c3bdfSopenharmony_ci		return;
224f08c3bdfSopenharmony_ci	}
225f08c3bdfSopenharmony_ci
226f08c3bdfSopenharmony_ci	if (check_error_event_metadata(event))
227f08c3bdfSopenharmony_ci		return;
228f08c3bdfSopenharmony_ci
229f08c3bdfSopenharmony_ci	info_error = get_event_info_error(event);
230f08c3bdfSopenharmony_ci	if (info_error)
231f08c3bdfSopenharmony_ci		fail += check_error_event_info_error(info_error, ex);
232f08c3bdfSopenharmony_ci	else {
233f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Generic error record not found");
234f08c3bdfSopenharmony_ci		fail++;
235f08c3bdfSopenharmony_ci	}
236f08c3bdfSopenharmony_ci
237f08c3bdfSopenharmony_ci	info_fid = get_event_info_fid(event);
238f08c3bdfSopenharmony_ci	if (info_fid)
239f08c3bdfSopenharmony_ci		fail += check_error_event_info_fid(info_fid, ex);
240f08c3bdfSopenharmony_ci	else {
241f08c3bdfSopenharmony_ci		tst_res(TFAIL, "FID record not found");
242f08c3bdfSopenharmony_ci		fail++;
243f08c3bdfSopenharmony_ci	}
244f08c3bdfSopenharmony_ci
245f08c3bdfSopenharmony_ci	if (!fail)
246f08c3bdfSopenharmony_ci		tst_res(TPASS, "Successfully received: %s", ex->name);
247f08c3bdfSopenharmony_ci}
248f08c3bdfSopenharmony_ci
249f08c3bdfSopenharmony_cistatic void do_test(unsigned int i)
250f08c3bdfSopenharmony_ci{
251f08c3bdfSopenharmony_ci	const struct test_case *tcase = &testcases[i];
252f08c3bdfSopenharmony_ci	size_t read_len;
253f08c3bdfSopenharmony_ci
254f08c3bdfSopenharmony_ci	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD|FAN_MARK_FILESYSTEM,
255f08c3bdfSopenharmony_ci			   FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
256f08c3bdfSopenharmony_ci
257f08c3bdfSopenharmony_ci	tcase->trigger_error();
258f08c3bdfSopenharmony_ci
259f08c3bdfSopenharmony_ci	read_len = SAFE_READ(0, fd_notify, event_buf, BUF_SIZE);
260f08c3bdfSopenharmony_ci
261f08c3bdfSopenharmony_ci	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_REMOVE|FAN_MARK_FILESYSTEM,
262f08c3bdfSopenharmony_ci			   FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
263f08c3bdfSopenharmony_ci
264f08c3bdfSopenharmony_ci	check_event(event_buf, read_len, tcase);
265f08c3bdfSopenharmony_ci	/* Unmount and mount the filesystem to get it out of the error state */
266f08c3bdfSopenharmony_ci	SAFE_UMOUNT(MOUNT_PATH);
267f08c3bdfSopenharmony_ci	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
268f08c3bdfSopenharmony_ci}
269f08c3bdfSopenharmony_ci
270f08c3bdfSopenharmony_cistatic void pre_corrupt_fs(void)
271f08c3bdfSopenharmony_ci{
272f08c3bdfSopenharmony_ci	SAFE_MKDIR(MOUNT_PATH"/"BASE_DIR, 0777);
273f08c3bdfSopenharmony_ci	SAFE_MKDIR(MOUNT_PATH"/"BAD_DIR, 0777);
274f08c3bdfSopenharmony_ci
275f08c3bdfSopenharmony_ci	fanotify_save_fid(MOUNT_PATH"/"BAD_DIR, &bad_file_fid);
276f08c3bdfSopenharmony_ci	fanotify_save_fid(MOUNT_PATH"/"BASE_DIR, &bad_link_fid);
277f08c3bdfSopenharmony_ci
278f08c3bdfSopenharmony_ci	SAFE_UMOUNT(MOUNT_PATH);
279f08c3bdfSopenharmony_ci	do_debugfs_request(tst_device->dev, "sif " BAD_DIR " mode 0xff");
280f08c3bdfSopenharmony_ci	do_debugfs_request(tst_device->dev, "ln <1> " BAD_LINK);
281f08c3bdfSopenharmony_ci	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
282f08c3bdfSopenharmony_ci}
283f08c3bdfSopenharmony_ci
284f08c3bdfSopenharmony_cistatic void init_null_fid(void)
285f08c3bdfSopenharmony_ci{
286f08c3bdfSopenharmony_ci	/* Use fanotify_save_fid to fill the fsid and overwrite the
287f08c3bdfSopenharmony_ci	 * file_handler to create a null_fid
288f08c3bdfSopenharmony_ci	 */
289f08c3bdfSopenharmony_ci	fanotify_save_fid(MOUNT_PATH, &null_fid);
290f08c3bdfSopenharmony_ci
291f08c3bdfSopenharmony_ci	null_fid.handle.handle_type = FILEID_INVALID;
292f08c3bdfSopenharmony_ci	null_fid.handle.handle_bytes = 0;
293f08c3bdfSopenharmony_ci}
294f08c3bdfSopenharmony_ci
295f08c3bdfSopenharmony_cistatic void setup(void)
296f08c3bdfSopenharmony_ci{
297f08c3bdfSopenharmony_ci	REQUIRE_FANOTIFY_EVENTS_SUPPORTED_ON_FS(FAN_CLASS_NOTIF|FAN_REPORT_FID,
298f08c3bdfSopenharmony_ci						FAN_MARK_FILESYSTEM,
299f08c3bdfSopenharmony_ci						FAN_FS_ERROR, ".");
300f08c3bdfSopenharmony_ci	pre_corrupt_fs();
301f08c3bdfSopenharmony_ci
302f08c3bdfSopenharmony_ci	fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF|FAN_REPORT_FID,
303f08c3bdfSopenharmony_ci				       O_RDONLY);
304f08c3bdfSopenharmony_ci
305f08c3bdfSopenharmony_ci	init_null_fid();
306f08c3bdfSopenharmony_ci}
307f08c3bdfSopenharmony_ci
308f08c3bdfSopenharmony_cistatic void cleanup(void)
309f08c3bdfSopenharmony_ci{
310f08c3bdfSopenharmony_ci	if (fd_notify > 0)
311f08c3bdfSopenharmony_ci		SAFE_CLOSE(fd_notify);
312f08c3bdfSopenharmony_ci}
313f08c3bdfSopenharmony_ci
314f08c3bdfSopenharmony_cistatic struct tst_test test = {
315f08c3bdfSopenharmony_ci	.test = do_test,
316f08c3bdfSopenharmony_ci	.tcnt = ARRAY_SIZE(testcases),
317f08c3bdfSopenharmony_ci	.setup = setup,
318f08c3bdfSopenharmony_ci	.cleanup = cleanup,
319f08c3bdfSopenharmony_ci	.mount_device = 1,
320f08c3bdfSopenharmony_ci	.mntpoint = MOUNT_PATH,
321f08c3bdfSopenharmony_ci	.needs_root = 1,
322f08c3bdfSopenharmony_ci	.dev_fs_type = "ext4",
323f08c3bdfSopenharmony_ci	.tags = (const struct tst_tag[]) {
324f08c3bdfSopenharmony_ci		{"linux-git", "124e7c61deb2"},
325f08c3bdfSopenharmony_ci		{}
326f08c3bdfSopenharmony_ci	},
327f08c3bdfSopenharmony_ci	.needs_cmds = (const char *[]) {
328f08c3bdfSopenharmony_ci		"debugfs",
329f08c3bdfSopenharmony_ci		NULL
330f08c3bdfSopenharmony_ci	}
331f08c3bdfSopenharmony_ci};
332f08c3bdfSopenharmony_ci
333f08c3bdfSopenharmony_ci#else
334f08c3bdfSopenharmony_ci	TST_TEST_TCONF("system does not have required name_to_handle_at() support");
335f08c3bdfSopenharmony_ci#endif
336f08c3bdfSopenharmony_ci#else
337f08c3bdfSopenharmony_ci	TST_TEST_TCONF("system doesn't have required fanotify support");
338f08c3bdfSopenharmony_ci#endif
339