From patchwork Thu Jul 11 22:37:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Flavio Cruz X-Patchwork-Id: 1959596 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=FBk9/mTO; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WKqQd0zQZz1xqc for ; Fri, 12 Jul 2024 08:39:05 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 434503838A24 for ; Thu, 11 Jul 2024 22:39:03 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-wm1-x330.google.com (mail-wm1-x330.google.com [IPv6:2a00:1450:4864:20::330]) by sourceware.org (Postfix) with ESMTPS id 730FC3858410 for ; Thu, 11 Jul 2024 22:37:39 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 730FC3858410 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 730FC3858410 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::330 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1720737500; cv=none; b=woFEfyd9iMIZLUe6lIUf3T+hd81MVC9oBjJA9iOUYKfMH4SZGHZY6FUFo5bLLmT+qFAQjZTWSfgewQiQ98QqA2cuYhh0PwFddz0Jm9ZYOrQGk3s+iFlp42qbr3TkOSy8/89dbOkUm9kQB7Z82zw7XkfB6yLSBRRolvkt5HpdEXE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1720737500; c=relaxed/simple; bh=NQL/RNNMzBzAp1akGan2hB1e11mBNMoMne5Sp60iGQM=; h=DKIM-Signature:Date:From:To:Subject:Message-ID:MIME-Version; b=t3084MwJR6WUgo61kRIxz01nbdE+nMnenzv9HgnppuAC5uODXMeAJ4D2Fqrlpa+w62J28OAn95T34WdHpkTAKIJK6CbsviTYWxShAxODivHd58WpoJgel4c2xXbfzLoepSoOvHYukgkB37+XRTienFB47FcsX8kLYk7bRpzfMys= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-wm1-x330.google.com with SMTP id 5b1f17b1804b1-4279f48bd94so502795e9.1 for ; Thu, 11 Jul 2024 15:37:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1720737458; x=1721342258; darn=sourceware.org; h=content-disposition:mime-version:message-id:subject:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=NMAmc11Rm29OZANx/zYNAg8i0RBOnSCdclFjXjlIdoQ=; b=FBk9/mTOx3OCHWbe1v0kNkL5rZyFEgGXUPqk0gblCyUCVbvdALK0IYILGY4TgZHc8y w5scURQuAEJ+VX8OBWhhbp6roglOWbuxKeDqG1UulGrwShGK07ioo/qqLIcoyIgpH0Mf Sp1NP3yVBLve2JqFMf5eMl/9ERfNlsaaiPpwcloYfSY2F19uvUjBxfOvtqKUl7wVuRWJ /xTgNn1Eprnp532WaT4ptsfBuy4d7oKOE/3UMmHZV23cZr0HcdsFw3kcCgjYxHT/j1hD vPL2WW611ObGlISIfSHx1bmYd+CfxFtva7MjP1LlmFmlIlShNE7Rlz06uAnyrh24dxuV KT8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720737458; x=1721342258; h=content-disposition:mime-version:message-id:subject:to:from:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=NMAmc11Rm29OZANx/zYNAg8i0RBOnSCdclFjXjlIdoQ=; b=OgJhWZSDKgJJtbpJN9aBBReoMi/tk1VsnLeADaqi+Wt0KK5+qjiEh9bMw4PsjiGJZm Lt/mKsfvq3XssbSLDHD/YWXjC0HTkIQg8baWeo72/TJa9yynmQpn16t+mQ6WbTTWnZW3 rUwZKXnvtiRNVuuJvdkeUlhTGy0SM7iJp/20FDHO/yFBiOGP+SPlonJsvwQPWbJeVs62 52Oj5TG9TkKQ+dXiAezGOjWkNojRrxYNirJK5ButMm0iq4oIb5IAnVxlvWFHv3byKHez x4yauotf+pODMJ+6L/EAelByp7ZjFTu5shCfYEU8JI0t22aEfC0ZTcH71f+Q9om7tC1s LMPQ== X-Forwarded-Encrypted: i=1; AJvYcCWG8+0z6VYdwR9TnZnIm6LWiSSksk/rxZVi23TguDGecRMEmjHHYWEvt+5OyfqFNhOhu8oGJMAoC5i4dqhnzpLsvVUVPC1q/7Va X-Gm-Message-State: AOJu0YwbBdWgUEkCr4jPkNL5fPjMSC9SMOvtbRtPkdeCPWqFwBiHmxEW uRtzGUqzIok1Veorsm6T0cRP6FjLIYwYCOIE+3cRQxIia2JzsAITCPZbXsA= X-Google-Smtp-Source: AGHT+IGNv/KmBs7KTbLwr9YtXuNrRUx9TPKakII0H/u/WNsVL/VatMGronagr4DMSaUb+rrjq+J/KQ== X-Received: by 2002:a05:600c:3b12:b0:426:5dd0:a1ee with SMTP id 5b1f17b1804b1-426707cc061mr62174445e9.2.1720737458022; Thu, 11 Jul 2024 15:37:38 -0700 (PDT) Received: from localhost ([2001:818:deb1:8200:914c:2525:f267:d039]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-426740daf6dsm49869945e9.1.2024.07.11.15.37.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Jul 2024 15:37:37 -0700 (PDT) Date: Thu, 11 Jul 2024 23:37:35 +0100 From: Flavio Cruz To: bug-hurd , libc-alpha Subject: [PATCH v2 glibc] Add pthread_getname_np and pthread_setname_np for Hurd Message-ID: <52acxwc5yvomilibanp4sx6ysprdyneu5c2b3ukdm2ac5iv3cc@eyn3xwsz3pbi> MIME-Version: 1.0 Content-Disposition: inline X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org We use thread_get_name and thread_set_name to get and set the thread name, so nothing is stored in the thread structure since these functions are supposed to be called sparingly. One notable difference with Linux is that the thread name is up to 32 chars, whereas Linux's is 16. Also added a mach_RPC_CHECK to check for the existing of gnumach RPCs. --- config.h.in | 6 ++ htl/Makefile | 2 + htl/Versions | 5 ++ sysdeps/htl/pthread.h | 11 +++ sysdeps/mach/configure | 76 +++++++++++++++++++-- sysdeps/mach/configure.ac | 29 ++++++-- sysdeps/mach/htl/pt-getname-np.c | 67 ++++++++++++++++++ sysdeps/mach/htl/pt-setname-np.c | 52 ++++++++++++++ sysdeps/mach/hurd/i386/libpthread.abilist | 2 + sysdeps/mach/hurd/x86_64/libpthread.abilist | 2 + 10 files changed, 239 insertions(+), 13 deletions(-) create mode 100644 sysdeps/mach/htl/pt-getname-np.c create mode 100644 sysdeps/mach/htl/pt-setname-np.c diff --git a/config.h.in b/config.h.in index 9a83b774..f495f112 100644 --- a/config.h.in +++ b/config.h.in @@ -159,6 +159,12 @@ /* Mach specific: define if the `host_page_size' RPC is available. */ #undef HAVE_HOST_PAGE_SIZE +/* Mach specific: define if the `thread_set_name' RPC is available. */ +#undef HAVE_MACH_THREAD_SET_NAME + +/* Mach specific: define if the `thread_get_name' RPC is available. */ +#undef HAVE_MACH_THREAD_GET_NAME + /* Mach/i386 specific: define if the `i386_io_perm_*' RPCs are available. */ #undef HAVE_I386_IO_PERM_MODIFY diff --git a/htl/Makefile b/htl/Makefile index 4028e5a2..c5d1c473 100644 --- a/htl/Makefile +++ b/htl/Makefile @@ -145,6 +145,8 @@ libpthread-routines := \ pt-getcpuclockid \ pt-setschedprio \ pt-yield \ + pt-getname-np \ + pt-setname-np \ sem_close \ sem-destroy \ sem-getvalue \ diff --git a/htl/Versions b/htl/Versions index 71005175..e1524117 100644 --- a/htl/Versions +++ b/htl/Versions @@ -169,6 +169,11 @@ libpthread { sem_clockwait; } + GLIBC_2.40 { + pthread_getname_np; + pthread_setname_np; + } + GLIBC_PRIVATE { __pthread_initialize_minimal; diff --git a/sysdeps/htl/pthread.h b/sysdeps/htl/pthread.h index fa626ebc..65837d10 100644 --- a/sysdeps/htl/pthread.h +++ b/sysdeps/htl/pthread.h @@ -891,6 +891,17 @@ extern int pthread_setschedparam (pthread_t __thr, int __policy, /* Set thread THREAD's scheduling priority. */ extern int pthread_setschedprio (pthread_t __thr, int __prio) __THROW; +#ifdef __USE_GNU +/* Get thread name visible in the kernel and its interfaces. */ +extern int pthread_getname_np (pthread_t __target_thread, char *__buf, + size_t __buflen) + __THROW __nonnull ((2)) __attr_access ((__write_only__, 2)); + +/* Set thread name visible in the kernel and its interfaces. */ +extern int pthread_setname_np (pthread_t __target_thread, const char *__name) + __THROW __nonnull ((2)) __attr_access ((__read_only__, 2)); +#endif + #ifdef __USE_GNU /* Yield the processor to another thread or process. This function is similar to the POSIX `sched_yield' function but diff --git a/sysdeps/mach/configure b/sysdeps/mach/configure index 5779efd1..cd21b26d 100644 --- a/sysdeps/mach/configure +++ b/sysdeps/mach/configure @@ -293,6 +293,9 @@ if test "x$mach_interface_list" = x; then as_fn_error $? "what manner of Mach is this?" "$LINENO" 5 fi + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 printf %s "checking for egrep -e... " >&6; } if test ${ac_cv_path_EGREP_TRADITIONAL+y} @@ -429,7 +432,7 @@ printf "%s\n" "$ac_cv_path_EGREP_TRADITIONAL" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for host_page_size in mach_host.defs" >&5 printf %s "checking for host_page_size in mach_host.defs... " >&6; } -if test ${libc_cv_mach_host_page_size+y} +if test ${libc_cv_mach_rpc_host_page_size+y} then : printf %s "(cached) " >&6 else case e in #( @@ -441,22 +444,83 @@ _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "host_page_size" >/dev/null 2>&1 then : - libc_cv_mach_host_page_size=yes + libc_cv_mach_rpc_host_page_size=yes else case e in #( - e) libc_cv_mach_host_page_size=no ;; + e) libc_cv_mach_rpc_host_page_size=no ;; esac fi rm -rf conftest* ;; esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_mach_host_page_size" >&5 -printf "%s\n" "$libc_cv_mach_host_page_size" >&6; } -if test $libc_cv_mach_host_page_size = yes; then +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_mach_rpc_host_page_size" >&5 +printf "%s\n" "$libc_cv_mach_rpc_host_page_size" >&6; } +if test $libc_cv_mach_rpc_host_page_size = yes; then printf "%s\n" "#define HAVE_HOST_PAGE_SIZE 1" >>confdefs.h fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for thread_set_name in gnumach.defs" >&5 +printf %s "checking for thread_set_name in gnumach.defs... " >&6; } +if test ${libc_cv_mach_rpc_thread_set_name+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "thread_set_name" >/dev/null 2>&1 +then : + libc_cv_mach_rpc_thread_set_name=yes +else case e in #( + e) libc_cv_mach_rpc_thread_set_name=no ;; +esac +fi +rm -rf conftest* + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_mach_rpc_thread_set_name" >&5 +printf "%s\n" "$libc_cv_mach_rpc_thread_set_name" >&6; } +if test $libc_cv_mach_rpc_thread_set_name = yes; then + printf "%s\n" "#define HAVE_MACH_THREAD_SET_NAME 1" >>confdefs.h + +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for thread_get_name in gnumach.defs" >&5 +printf %s "checking for thread_get_name in gnumach.defs... " >&6; } +if test ${libc_cv_mach_rpc_thread_get_name+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "thread_get_name" >/dev/null 2>&1 +then : + libc_cv_mach_rpc_thread_get_name=yes +else case e in #( + e) libc_cv_mach_rpc_thread_get_name=no ;; +esac +fi +rm -rf conftest* + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_mach_rpc_thread_get_name" >&5 +printf "%s\n" "$libc_cv_mach_rpc_thread_get_name" >&6; } +if test $libc_cv_mach_rpc_thread_get_name = yes; then + printf "%s\n" "#define HAVE_MACH_THREAD_GET_NAME 1" >>confdefs.h + +fi + + ac_fn_c_check_header_preproc "$LINENO" "mach/machine/ndr_def.h" "ac_cv_header_mach_machine_ndr_def_h" if test "x$ac_cv_header_mach_machine_ndr_def_h" = xyes then : diff --git a/sysdeps/mach/configure.ac b/sysdeps/mach/configure.ac index 730fb25d..648035e8 100644 --- a/sysdeps/mach/configure.ac +++ b/sysdeps/mach/configure.ac @@ -72,14 +72,29 @@ if test "x$mach_interface_list" = x; then AC_MSG_ERROR([what manner of Mach is this?]) fi -AC_CACHE_CHECK(for host_page_size in mach_host.defs, - libc_cv_mach_host_page_size, [dnl -AC_EGREP_HEADER(host_page_size, mach/mach_host.defs, - libc_cv_mach_host_page_size=yes, - libc_cv_mach_host_page_size=no)]) -if test $libc_cv_mach_host_page_size = yes; then - AC_DEFINE([HAVE_HOST_PAGE_SIZE]) +dnl +dnl mach_RPC_CHECK(interface.defs, rpc_method, define) +dnl +dnl Check if rpc_method RPC is defined by interface.defs +dnl and define `define`. +dnl +AC_DEFUN([mach_RPC_CHECK], [dnl +AC_CACHE_CHECK(for $2 in $1, libc_cv_mach_rpc_$2, [dnl +AC_EGREP_HEADER($2, mach/$1, + libc_cv_mach_rpc_$2=yes, + libc_cv_mach_rpc_$2=no)]) +if test $libc_cv_mach_rpc_$2 = yes; then + AC_DEFINE([$3]) fi +]) + + +mach_RPC_CHECK(mach_host.defs, host_page_size, + HAVE_HOST_PAGE_SIZE) +mach_RPC_CHECK(gnumach.defs, thread_set_name, + HAVE_MACH_THREAD_SET_NAME) +mach_RPC_CHECK(gnumach.defs, thread_get_name, + HAVE_MACH_THREAD_GET_NAME) AC_CHECK_HEADER(mach/machine/ndr_def.h, [dnl DEFINES="$DEFINES -DNDR_DEF_HEADER=''"], [dnl diff --git a/sysdeps/mach/htl/pt-getname-np.c b/sysdeps/mach/htl/pt-getname-np.c new file mode 100644 index 00000000..8943474b --- /dev/null +++ b/sysdeps/mach/htl/pt-getname-np.c @@ -0,0 +1,67 @@ +/* pthread_getname_np. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +#include + +int +__pthread_getname_np (pthread_t thread, char *buf, size_t len) +{ +#ifdef HAVE_MACH_THREAD_GET_NAME +/* GNU Mach doesn't export this so we have to define it ourselves. */ +#define MACH_THREAD_NAME_MAX 32 + struct __pthread *pthread; + error_t err; + kernel_debug_name_t tmp; + + /* Note that we don't check for len to be MACH_THREAD_NAME_MAX + * since we want to be more compatible with the Linux API which + * requires that the buffer is at least 16 bytes long. + * + * We check for at least 1 byte since we truncate the result below. */ + if (len < 1) + return ERANGE; + if (len > MACH_THREAD_NAME_MAX) + len = MACH_THREAD_NAME_MAX; + + /* Lookup the thread structure for THREAD. */ + pthread = __pthread_getid (thread); + if (pthread == NULL) + return ESRCH; + + /* __thread_get_name expects a buffer of size sizeof (kernel_debug_name_t) + * and anything smaller will overflow. */ + err = __thread_get_name (pthread->kernel_thread, tmp); + if (err != KERN_SUCCESS) + return __hurd_fail (err); + /* Truncate the source name to fit in the destination buffer. */ + tmp[len - 1] = '\0'; + memcpy (buf, tmp, len); + + return 0; +#else + return ENOTSUP; +#endif +} + +weak_alias (__pthread_getname_np, pthread_getname_np) diff --git a/sysdeps/mach/htl/pt-setname-np.c b/sysdeps/mach/htl/pt-setname-np.c new file mode 100644 index 00000000..2fd66730 --- /dev/null +++ b/sysdeps/mach/htl/pt-setname-np.c @@ -0,0 +1,52 @@ +/* pthread_setname_np. Mach version. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include + +#include + +int +__pthread_setname_np (pthread_t thread, const char *name) +{ +#ifdef HAVE_MACH_THREAD_SET_NAME +/* GNU Mach doesn't export this so we have to define it ourselves. */ +#define MACH_THREAD_NAME_MAX 32 + struct __pthread *pthread; + error_t err; + + /* Lookup the thread structure for THREAD. */ + pthread = __pthread_getid (thread); + if (pthread == NULL) + return ESRCH; + + if (strlen (name) >= MACH_THREAD_NAME_MAX) + return ERANGE; + + err = __thread_set_name (pthread->kernel_thread, name); + if (err != KERN_SUCCESS) + return __hurd_fail (err); + return 0; +#else + return ENOTSUP; +#endif +} + +weak_alias (__pthread_setname_np, pthread_setname_np) diff --git a/sysdeps/mach/hurd/i386/libpthread.abilist b/sysdeps/mach/hurd/i386/libpthread.abilist index fa90cc65..3ea7cb41 100644 --- a/sysdeps/mach/hurd/i386/libpthread.abilist +++ b/sysdeps/mach/hurd/i386/libpthread.abilist @@ -164,3 +164,5 @@ GLIBC_2.32 tss_create F GLIBC_2.32 tss_delete F GLIBC_2.32 tss_get F GLIBC_2.32 tss_set F +GLIBC_2.40 pthread_getname_np F +GLIBC_2.40 pthread_setname_np F diff --git a/sysdeps/mach/hurd/x86_64/libpthread.abilist b/sysdeps/mach/hurd/x86_64/libpthread.abilist index 80615d16..69999df5 100644 --- a/sysdeps/mach/hurd/x86_64/libpthread.abilist +++ b/sysdeps/mach/hurd/x86_64/libpthread.abilist @@ -163,3 +163,5 @@ GLIBC_2.38 tss_create F GLIBC_2.38 tss_delete F GLIBC_2.38 tss_get F GLIBC_2.38 tss_set F +GLIBC_2.40 pthread_getname_np F +GLIBC_2.40 pthread_setname_np F