+#if HFS_COMPRESSION
+/*
+ * hfs_ref_data_vp(): returns the data fork vnode for a given cnode.
+ * In the (hopefully rare) case where the data fork vnode is not
+ * present, it will use hfs_vget() to create a new vnode for the
+ * data fork.
+ *
+ * NOTE: If successful and a vnode is returned, the caller is responsible
+ * for releasing the returned vnode with vnode_rele().
+ */
+static int
+hfs_ref_data_vp(struct cnode *cp, struct vnode **data_vp, int skiplock)
+{
+ int vref = 0;
+
+ if (!data_vp || !cp) /* sanity check incoming parameters */
+ return EINVAL;
+
+ /* maybe we should take the hfs cnode lock here, and if so, use the skiplock parameter to tell us not to */
+
+ if (!skiplock) hfs_lock(cp, HFS_SHARED_LOCK);
+ struct vnode *c_vp = cp->c_vp;
+ if (c_vp) {
+ /* we already have a data vnode */
+ *data_vp = c_vp;
+ vref = vnode_ref(*data_vp);
+ if (!skiplock) hfs_unlock(cp);
+ if (vref == 0) {
+ return 0;
+ }
+ return EINVAL;
+ }
+ /* no data fork vnode in the cnode, so ask hfs for one. */
+
+ if (!cp->c_rsrc_vp) {
+ /* if we don't have either a c_vp or c_rsrc_vp, we can't really do anything useful */
+ *data_vp = NULL;
+ if (!skiplock) hfs_unlock(cp);
+ return EINVAL;
+ }
+
+ if (0 == hfs_vget(VTOHFS(cp->c_rsrc_vp), cp->c_cnid, data_vp, 1, 0) &&
+ 0 != data_vp) {
+ vref = vnode_ref(*data_vp);
+ vnode_put(*data_vp);
+ if (!skiplock) hfs_unlock(cp);
+ if (vref == 0) {
+ return 0;
+ }
+ return EINVAL;
+ }
+ /* there was an error getting the vnode */
+ *data_vp = NULL;
+ if (!skiplock) hfs_unlock(cp);
+ return EINVAL;
+}
+
+/*
+ * hfs_lazy_init_decmpfs_cnode(): returns the decmpfs_cnode for a cnode,
+ * allocating it if necessary; returns NULL if there was an allocation error
+ */
+static decmpfs_cnode *
+hfs_lazy_init_decmpfs_cnode(struct cnode *cp)
+{
+ if (!cp->c_decmp) {
+ decmpfs_cnode *dp = NULL;
+ MALLOC_ZONE(dp, decmpfs_cnode *, sizeof(decmpfs_cnode), M_DECMPFS_CNODE, M_WAITOK);
+ if (!dp) {
+ /* error allocating a decmpfs cnode */
+ return NULL;
+ }
+ decmpfs_cnode_init(dp);
+ if (!OSCompareAndSwapPtr(NULL, dp, (void * volatile *)&cp->c_decmp)) {
+ /* another thread got here first, so free the decmpfs_cnode we allocated */
+ decmpfs_cnode_destroy(dp);
+ FREE_ZONE(dp, sizeof(*dp), M_DECMPFS_CNODE);
+ }
+ }
+
+ return cp->c_decmp;
+}
+
+/*
+ * hfs_file_is_compressed(): returns 1 if the file is compressed, and 0 (zero) if not.
+ * if the file's compressed flag is set, makes sure that the decmpfs_cnode field
+ * is allocated by calling hfs_lazy_init_decmpfs_cnode(), then makes sure it is populated,
+ * or else fills it in via the decmpfs_file_is_compressed() function.
+ */
+int
+hfs_file_is_compressed(struct cnode *cp, int skiplock)
+{
+ int ret = 0;
+
+ /* fast check to see if file is compressed. If flag is clear, just answer no */
+ if (!(cp->c_bsdflags & UF_COMPRESSED)) {
+ return 0;
+ }
+
+ decmpfs_cnode *dp = hfs_lazy_init_decmpfs_cnode(cp);
+ if (!dp) {
+ /* error allocating a decmpfs cnode, treat the file as uncompressed */
+ return 0;
+ }
+
+ /* flag was set, see if the decmpfs_cnode state is valid (zero == invalid) */
+ uint32_t decmpfs_state = decmpfs_cnode_get_vnode_state(dp);
+ switch(decmpfs_state) {
+ case FILE_IS_COMPRESSED:
+ case FILE_IS_CONVERTING: /* treat decompressing files as if they are compressed */
+ return 1;
+ case FILE_IS_NOT_COMPRESSED:
+ return 0;
+ /* otherwise the state is not cached yet */
+ }
+
+ /* decmpfs hasn't seen this file yet, so call decmpfs_file_is_compressed() to init the decmpfs_cnode struct */
+ struct vnode *data_vp = NULL;
+ if (0 == hfs_ref_data_vp(cp, &data_vp, skiplock)) {
+ if (data_vp) {
+ ret = decmpfs_file_is_compressed(data_vp, VTOCMP(data_vp)); // fill in decmpfs_cnode
+ vnode_rele(data_vp);
+ }
+ }
+ return ret;
+}
+
+/* hfs_uncompressed_size_of_compressed_file() - get the uncompressed size of the file.
+ * if the caller has passed a valid vnode (has a ref count > 0), then hfsmp and fid are not required.
+ * if the caller doesn't have a vnode, pass NULL in vp, and pass valid hfsmp and fid.
+ * files size is returned in size (required)
+ * if the indicated file is a directory (or something that doesn't have a data fork), then this call
+ * will return an error and the caller should fall back to treating the item as an uncompressed file
+ */
+int
+hfs_uncompressed_size_of_compressed_file(struct hfsmount *hfsmp, struct vnode *vp, cnid_t fid, off_t *size, int skiplock)
+{
+ int ret = 0;
+ int putaway = 0; /* flag to remember if we used hfs_vget() */
+
+ if (!size) {
+ return EINVAL; /* no place to put the file size */
+ }
+
+ if (NULL == vp) {
+ if (!hfsmp || !fid) { /* make sure we have the required parameters */
+ return EINVAL;
+ }
+ if (0 != hfs_vget(hfsmp, fid, &vp, skiplock, 0)) { /* vnode is null, use hfs_vget() to get it */
+ vp = NULL;
+ } else {
+ putaway = 1; /* note that hfs_vget() was used to aquire the vnode */
+ }
+ }
+ /* this double check for compression (hfs_file_is_compressed)
+ * ensures the cached size is present in case decmpfs hasn't
+ * encountered this node yet.
+ */
+ if (vp) {
+ if (hfs_file_is_compressed(VTOC(vp), skiplock) ) {
+ *size = decmpfs_cnode_get_vnode_cached_size(VTOCMP(vp)); /* file info will be cached now, so get size */
+ } else {
+ if (VTOCMP(vp) && VTOCMP(vp)->cmp_type >= CMP_MAX) {
+ if (VTOCMP(vp)->cmp_type != DATALESS_CMPFS_TYPE) {
+ // if we don't recognize this type, just use the real data fork size
+ if (VTOC(vp)->c_datafork) {
+ *size = VTOC(vp)->c_datafork->ff_size;
+ ret = 0;
+ } else {
+ ret = EINVAL;
+ }
+ } else {
+ *size = decmpfs_cnode_get_vnode_cached_size(VTOCMP(vp)); /* file info will be cached now, so get size */
+ ret = 0;
+ }
+ } else {
+ ret = EINVAL;
+ }
+ }
+ }
+
+ if (putaway) { /* did we use hfs_vget() to get this vnode? */
+ vnode_put(vp); /* if so, release it and set it to null */
+ vp = NULL;
+ }
+ return ret;
+}
+
+int
+hfs_hides_rsrc(vfs_context_t ctx, struct cnode *cp, int skiplock)
+{
+ if (ctx == decmpfs_ctx)
+ return 0;
+ if (!hfs_file_is_compressed(cp, skiplock))
+ return 0;
+ return decmpfs_hides_rsrc(ctx, cp->c_decmp);
+}
+
+int
+hfs_hides_xattr(vfs_context_t ctx, struct cnode *cp, const char *name, int skiplock)
+{
+ if (ctx == decmpfs_ctx)
+ return 0;
+ if (!hfs_file_is_compressed(cp, skiplock))
+ return 0;
+ return decmpfs_hides_xattr(ctx, cp->c_decmp, name);
+}
+#endif /* HFS_COMPRESSION */
+