+/*
+ * nfs_refresh_fh will attempt to update the file handle for the node.
+ *
+ * It only does this for symbolic links and regular files that are not currently opened.
+ *
+ * On Success returns 0 and the nodes file handle is updated, or ESTALE on failure.
+ */
+int
+nfs_refresh_fh(nfsnode_t np, vfs_context_t ctx)
+{
+ vnode_t dvp, vp = NFSTOV(np);
+ nfsnode_t dnp;
+ const char *v_name = vnode_getname(vp);
+ char *name;
+ int namelen, refreshed;
+ uint32_t fhsize;
+ int error, wanted = 0;
+ uint8_t *fhp;
+ struct timespec ts = {.tv_sec = 2, .tv_nsec = 0};
+
+ NFS_VNOP_DBG("vnode is %d\n", vnode_vtype(vp));
+
+ dvp = vnode_parent(vp);
+ if ((vnode_vtype(vp) != VREG && vnode_vtype(vp) != VLNK) ||
+ v_name == NULL || *v_name == '\0' || dvp == NULL) {
+ if (v_name != NULL) {
+ vnode_putname(v_name);
+ }
+ return ESTALE;
+ }
+ dnp = VTONFS(dvp);
+
+ namelen = NFS_STRLEN_INT(v_name);
+ MALLOC(name, char *, namelen + 1, M_TEMP, M_WAITOK);
+ if (name == NULL) {
+ vnode_putname(v_name);
+ return ESTALE;
+ }
+ bcopy(v_name, name, namelen + 1);
+ NFS_VNOP_DBG("Trying to refresh %s : %s\n", v_name, name);
+ vnode_putname(v_name);
+
+ /* Allocate the maximum size file handle */
+ MALLOC(fhp, uint8_t *, NFS4_FHSIZE, M_FHANDLE, M_WAITOK);
+ if (fhp == NULL) {
+ FREE(name, M_TEMP);
+ return ESTALE;
+ }
+
+ if ((error = nfs_node_lock(np))) {
+ FREE(name, M_TEMP);
+ FREE(fhp, M_FHANDLE);
+ return ESTALE;
+ }
+
+ fhsize = np->n_fhsize;
+ bcopy(np->n_fhp, fhp, fhsize);
+ while (ISSET(np->n_flag, NREFRESH)) {
+ SET(np->n_flag, NREFRESHWANT);
+ NFS_VNOP_DBG("Waiting for refresh of %s\n", name);
+ msleep(np, &np->n_lock, PZERO - 1, "nfsrefreshwant", &ts);
+ if ((error = nfs_sigintr(NFSTONMP(np), NULL, vfs_context_thread(ctx), 0))) {
+ break;
+ }
+ }
+ refreshed = error ? 0 : !NFS_CMPFH(np, fhp, fhsize);
+ SET(np->n_flag, NREFRESH);
+ nfs_node_unlock(np);
+
+ NFS_VNOP_DBG("error = %d, refreshed = %d\n", error, refreshed);
+ if (error || refreshed) {
+ goto nfsmout;
+ }
+
+ /* Check that there are no open references for this file */
+ lck_mtx_lock(&np->n_openlock);
+ if (np->n_openrefcnt || !TAILQ_EMPTY(&np->n_opens) || !TAILQ_EMPTY(&np->n_lock_owners)) {
+ int cnt = 0;
+ struct nfs_open_file *ofp;
+
+ TAILQ_FOREACH(ofp, &np->n_opens, nof_link) {
+ cnt += ofp->nof_opencnt;
+ }
+ if (cnt) {
+ lck_mtx_unlock(&np->n_openlock);
+ NFS_VNOP_DBG("Can not refresh file handle for %s with open state\n", name);
+ NFS_VNOP_DBG("\topenrefcnt = %d, opens = %d lock_owners = %d\n",
+ np->n_openrefcnt, cnt, !TAILQ_EMPTY(&np->n_lock_owners));
+ error = ESTALE;
+ goto nfsmout;
+ }
+ }
+ lck_mtx_unlock(&np->n_openlock);
+ /*
+ * Since the FH is currently stale we should not be able to
+ * establish any open state until the FH is refreshed.
+ */
+
+ error = nfs_node_lock(np);
+ nfsmout_if(error);
+ /*
+ * Symlinks should never need invalidations and are holding
+ * the one and only nfsbuf in an uncached acquired state
+ * trying to do a readlink. So we will hang if we invalidate
+ * in that case. Only in in the VREG case do we need to
+ * invalidate.
+ */
+ if (vnode_vtype(vp) == VREG) {
+ np->n_flag &= ~NNEEDINVALIDATE;
+ nfs_node_unlock(np);
+ error = nfs_vinvalbuf(vp, V_IGNORE_WRITEERR, ctx, 1);
+ if (error) {
+ NFS_VNOP_DBG("nfs_vinvalbuf returned %d\n", error);
+ }
+ nfsmout_if(error);
+ } else {
+ nfs_node_unlock(np);
+ }
+
+ NFS_VNOP_DBG("Looking up %s\n", name);
+ error = nfs_lookitup(dnp, name, namelen, ctx, &np);
+ if (error) {
+ NFS_VNOP_DBG("nfs_lookitup returned %d\n", error);
+ }
+
+nfsmout:
+ nfs_node_lock_force(np);
+ wanted = ISSET(np->n_flag, NREFRESHWANT);
+ CLR(np->n_flag, NREFRESH | NREFRESHWANT);
+ nfs_node_unlock(np);
+ if (wanted) {
+ wakeup(np);
+ }
+
+ if (error == 0) {
+ NFS_VNOP_DBG("%s refreshed file handle\n", name);
+ }
+
+ FREE(name, M_TEMP);
+ FREE(fhp, M_FHANDLE);
+
+ return error ? ESTALE : 0;
+}