/*static char *SCCSID = "@(#)setinfo.c  6.4 92/02/21";*/
#define _BTXT _BTXT

#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsh.h>
#include "api.h"
#include "const.h"
#include "misc.h"
#include "chain.h"
#include "buf.h"
#include "dir.h"
#include "storage.h"
#include "volume.h"
#include "tables.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "assert.h"
#include "notify.h"
#include "log.h"
#include "pr_CACHE.h"
#include "pr_DIR.h"
#include "pr_FCN.h"
#include "pr_FSSUB.h"
#include "pr_MISC.h"
#include "pr_WRSUB.h"

// @76193  #ifdef DEBUG_HOLDS
#include "pr_debug.h"
// @76193  #endif

/**     SetInfoToPoft - put info into a directory entry via a POFT
 *
 *      ENTRY   (pde) = pointer to directory entry of interest
 *              (pbuf) = buffer header containing pde
 *              (patt) = pointer to fileinfo structure
 *      EXIT    error return if ptms is bogus buffer, else NO_ERROR
 *              Yields CPU
 *
 * Change history:
 * ---------------
 * @76193  17-Aug-1993 by D.Azzarito - Change holds for LOG support
 * 74243 Nizinski 09/28/93 Only set the OFM_Finfo_Lev1 flag if the user
 *                         has really changed the access or modification
 *                         time stamps. If the user does a queryinfo
 *                         prior to the setinfo, the user can pass the
 *                         timestamps from the queryinfo (without
 *                         modifying them), so we don't want to set
 *                         the OFM_Finfo_Lev1 flag because the
 *                         user isn't really updating the timestamp
 *                         in this case.
 */

USHORT SetInfoToPoft (POFT poft,
                      PATTRIBUTES patt,
                      PUCHAR pucDosAttr,
                      PFRAME pfrm)
{
   PDIRENT pde;
   PBUFNODE pbuf;
   USHORT erc;

   DOSDATE savpattddAccess;                                /* 74243 */
   DOSTIME savpattdtAccess;                                /* 74243 */
   DOSDATE savpattddMod;                                   /* 74243 */
   DOSTIME savpattdtMod;                                   /* 74243 */

   ASSERT_OFT(poft);

   /* going to write out, so set DMask to this file */
   pfrm->flDMask = poft->flDMask;

   if ((poft->bFlag & (OTF_LCK + OTF_PLK)) != 0)
      HoldOFT(poft,pfrm);

   IncrHoldsOFT(poft);

   if ((pde = RDE(poft,&pbuf,pfrm)) == NULL)
      return GetHrdErr_();

   /* Save the original timestamps from the pde for later comparison to see if
    * the user has modified them.
    * Note that simply saving the pde times and comparing them later doesn't
    * work since the time conversion routines are not very precise. Doing it
    * this way ensures that the saved patt time values match those which the
    * user would have obtained via DosQFileInfo.
    * Remember, however, that if the user passes zeros for the timestamp,
    * that the user does not wish to modify the timestamp.           74243
    */
   Net_To_Dos_Date (pde->timLastAccess,
                    &savpattddAccess,
                    &savpattdtAccess);                            /* 74243 */
   Net_To_Dos_Date (pde->timLastMod,
                    &savpattddMod,
                    &savpattdtMod);                               /* 74243 */

   ASSERT_BUF(pbuf);

   /* hold this, since SetInfoToPde can block */
   IncrHoldsBUF(pbuf);  // @76193

   /* The notify check must be done here since we don't have the SUBDIR ptr
    * available in SetInfoToPde.
    */

   if (((erc = SetInfoToPde(pde,pbuf,patt,FALSE,pfrm)) == NO_ERROR) &&
       ((erc = ProbeAddress(PB_OPREAD,(PCHAR)patt,
                            sizeof(*patt),pfrm)) == NO_ERROR)) {

      if (pucDosAttr != NULL) {
         *pucDosAttr = (BYTE)(pde->fAttr & ~ATTR_NEWNAME);
      }

      if (MAKETYPE(patt->ddAccess,USHORT) | MAKETYPE(patt->dtAccess,USHORT))
      {
        if ((MAKETYPE(savpattddAccess,USHORT) !=          /* 74243 */
             MAKETYPE(patt->ddAccess,USHORT)) |           /* 74243 */
            (MAKETYPE(savpattdtAccess,USHORT) !=          /* 74243 */
             MAKETYPE(patt->dtAccess,USHORT)))            /* 74243 */
                  /* we changed the last access date/time, so forget any
                   * previous reads.  (This prevents close from doing the update.)
                   */

                     poft->bMFlag |= (OFM_Read | OFM_FInfo_Lev1);
      }

      if (MAKETYPE(patt->ddMod,USHORT) | MAKETYPE(patt->dtMod,USHORT))
      {
        if ((MAKETYPE(savpattddMod,USHORT) !=             /* 74243 */
             MAKETYPE(patt->ddMod,USHORT)) |              /* 74243 */
            (MAKETYPE(savpattdtMod,USHORT) !=             /* 74243 */
             MAKETYPE(patt->dtMod,USHORT)))               /* 74243 */

                  /* we changed the last modification date/time, so forget any
                  * previous writes.  (This prevents close from doing the update.)
                   */

                     poft->bMFlag |= (OFM_Written | OFM_FInfo_Lev1);
      }

      NotifyCheck(N_SETTIMES,poft->psbdDir,pde,pfrm);
   }

   DecrHoldsBUF(pbuf);  // @76193

   DecrHoldsSBD(poft->psbdDir);

   if (poft->psbdDir->bFlags & SDF_PND)
      ChkHoldDir (poft->psbdDir);

   UnHoldOFT (poft);

   return GetHardErr(erc);
}


/**     SetInfoToPde - put info into directory entry
 *
 *      The only subtlety is that if both time/date fields are zero, we
 *      skip setting that time.
 *
 *      NOTE:  to maintain compatibility with FAT, we bend the
 *             spec a little.  Zero date fields are accepted--
 *             if the accompanying time-of-day field is non-zero,
 *             the time on the file is set, but the date is left
 *             unchanged.  (If both are zero, the date and time
 *             fields are both left unchanged, as specified by
 *             the spec.)
 *
 *
 *      ENTRY   (pde) = pointer to directory entry of interest
 *              (pbuf) = buffer header containing pde
 *              (patt) = pointer to fileinfo structure
 *      EXIT    error return if ptms is bogus buffer, else NO_ERROR
 *
 * Change history:
 * ---------------
 * 08/26/93 72740 LRN also pass pde to ValidateSetInfo.
 */
USHORT SetInfoToPde (PDIRENT pde, PBUFNODE pbuf, PATTRIBUTES patt,
                    BOOL fWrtThru, PFRAME pfrm)
{
   DOSDATE ddTmp;
   DOSTIME dtTmp;
   BYTE    tmpAttr;
   USHORT  erc;

   IncrHoldsBUF (pbuf);  // @76193
   erc = ProbeAddress(PB_OPREAD,(PCHAR)patt,sizeof(*patt),pfrm);
   DecrHoldsBUF (pbuf);  // @76193

   if (erc == NO_ERROR) {

      if ((erc = ValidateSetInfo(patt,pde)) == NO_ERROR) {

         if (MAKETYPE(patt->ddCreate,USHORT)) {
            /*  non-zero date--update date and time.  */

            pde->timCreate = Dos_To_Net_Date(patt->ddCreate,patt->dtCreate);

         } else {
            /*  zero date--if time is non-zero, update it.  */

            if (MAKETYPE(patt->dtCreate,USHORT)) {

               Net_To_Dos_Date(pde->timCreate,&ddTmp,&dtTmp);
               pde->timCreate = Dos_To_Net_Date(ddTmp,patt->dtCreate);

            }
         }

         if (MAKETYPE(patt->ddAccess,USHORT)) {
            /*  non-zero date--update date and time.  */

            pde->timLastAccess = Dos_To_Net_Date(patt->ddAccess,patt->dtAccess);

         } else {
            /*  zero date--if time is non-zero, update it.  */

            if (MAKETYPE(patt->dtAccess,USHORT)) {

               Net_To_Dos_Date(pde->timLastAccess,&ddTmp,&dtTmp);
               pde->timLastAccess = Dos_To_Net_Date(ddTmp,patt->dtAccess);

            }
         }
         if (MAKETYPE(patt->ddMod,USHORT)) {
            /*  non-zero date--update date and time.  */

            pde->timLastMod = Dos_To_Net_Date(patt->ddMod,patt->dtMod);

         } else {
            /*  zero date--if time is non-zero, update it.  */

            if (MAKETYPE(patt->dtMod,USHORT)) {

               Net_To_Dos_Date(pde->timLastMod,&ddTmp,&dtTmp);
               pde->timLastMod = Dos_To_Net_Date(ddTmp,patt->dtMod);

            }
         }

         tmpAttr = pde->fAttr & (BYTE)(~ATTR_CHANGEABLE);
         pde->fAttr = (BYTE)((BYTE)(patt->usFileAttr) | tmpAttr);
      }
   }

   if (erc == NO_ERROR) {
      SetDEDirt(pde,pbuf,fWrtThru,pfrm);
   }
   return(erc);
}

/**     ValidateSetInfo - Make sure passed buffer info is valid before
 *                        putting it into the directory entry
 *
 *      ENTRY   (patt) = pointer to user's input setinfo buffer
 *       (pde)  = pointer to directory entry
 *
 *      EXIT    error return if any part of the input buffer is invalid
 *
 * Change history:
 * ---------------
 * 08/26/93 72740 LRN allow user to pass in non-changeable attributes if
 *                    these are already set in the dirent. The user may
 *                    have done a DosQueryPathInfo on a directory, then called
 *                    DosSetPathInfo without modifying the contents of the
 *                    attributes...hence they are not trying to change them.
 *                    This change requires the pde to be passed to us.
 * 12/09/93 76540 LRN previous change to 72740 did not correctly handle
 *                    the case of long file names, which will have the
 *                    ATTR_NEWFILE bit set in the dirent. This attribute
 *                    is not passed back to the user on a query info, so
 *                    the test for patt->usFileAttr = pde->fAttr was
 *                    not correct.
 */

USHORT ValidateSetInfo (PATTRIBUTES patt, PDIRENT pde)
{
    USHORT erc;
    BYTE   attr_minus_dir = (BYTE)(patt->usFileAttr & (~ATTR_DIRECTORY)); /* 76540 */

    erc = ValidateDateTime (patt->ddCreate, patt->dtCreate);

    if (erc == NO_ERROR) {
        erc = ValidateDateTime (patt->ddAccess, patt->dtAccess);
    }

    if (erc == NO_ERROR) {
        erc = ValidateDateTime (patt->ddMod, patt->dtMod);
    }

    if (erc == NO_ERROR) {
       if (attr_minus_dir & (~ATTR_CHANGEABLE))                   /* 76540 */
          return(ERROR_INVALID_PARAMETER);                        /* 76540 */

       if (pde ->fAttr & ATTR_DIRECTORY) {                      // #142523
         if (!((BYTE)(patt->usFileAttr & ATTR_DIRECTORY)))      // #142523
           return(ERROR_DIRECTORY);                             // #142523
       }
    }

    return(erc);
}


/**     ValidateDateTime - checks months & days fields for valid ranges
 *
 *      ENTRY   (ddate) = date (DOSDATE format)
 *              (dtime) = time (DOSTIME format)
 *
 *      EXIT    error return if months, days, hours, minutes, or seconds
 *              are out of range
 *
 *      NOTE:  to maintain FAT-compatibility, a zero date is accepted.
 */

USHORT ValidateDateTime (DOSDATE ddate, DOSTIME dtime)
{
   if (MAKETYPE(ddate,USHORT)) {
      /*  non-zero date.  Time and date must be valid.  */

      if (dtime.seconds2 > 29)
         dtime.seconds2 = 29;

      if ((ddate.months == 0)  ||
          (ddate.months > 12)  ||
          (ddate.days == 0)    ||
          (dtime.hours > 23)   ||
          (dtime.minutes > 59))
         return(ERROR_INVALID_PARAMETER);

   } else {
      /*   zero date.  Time must be valid.  */

      if (dtime.seconds2 > 29)
         dtime.seconds2 = 29;

      if ((dtime.hours > 23)   ||
          (dtime.minutes > 59))
         return(ERROR_INVALID_PARAMETER);

   }
   return(NO_ERROR);
}
